cancan-microstack 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cancan_microstack/__init__.py +14 -0
- cancan_microstack/__version__.py +10 -0
- cancan_microstack/assets/__init__.py +6 -0
- cancan_microstack/assets/builds/caddy/Caddyfile +187 -0
- cancan_microstack/assets/builds/caddy/DEPLOYMENT.md +303 -0
- cancan_microstack/assets/builds/caddy/Dockerfile +46 -0
- cancan_microstack/assets/builds/caddy/README.md +343 -0
- cancan_microstack/assets/builds/caddy/geoip/README.md +5 -0
- cancan_microstack/assets/builds/caddy/start.sh +78 -0
- cancan_microstack/assets/builds/caddy/waf/coraza.conf +179 -0
- cancan_microstack/assets/builds/service/Dockerfile +59 -0
- cancan_microstack/assets/builds/service/README.md +13 -0
- cancan_microstack/assets/ddl/create_db.sql +22 -0
- cancan_microstack/assets/ddl/infra/execution_log_tbl.sql +46 -0
- cancan_microstack/assets/ddl/infra/node_instance_tbl.sql +56 -0
- cancan_microstack/assets/ddl/infra/service_action_log_tbl.sql +36 -0
- cancan_microstack/assets/ddl/infra/service_config_tbl.sql +26 -0
- cancan_microstack/assets/ddl/infra/service_info_tbl.sql +45 -0
- cancan_microstack/assets/ddl/infra/service_instance_tbl.sql +54 -0
- cancan_microstack/assets/ddl/infra/service_operation_tbl.sql +47 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_tbl.sql +60 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_version_tbl.sql +35 -0
- cancan_microstack/assets/ddl/infra/workflow_engine_alert_tbl.sql +34 -0
- cancan_microstack/assets/ddl/infra/workflow_run_tbl.sql +52 -0
- cancan_microstack/assets/ddl/ops/admin_user_tbl.sql +34 -0
- cancan_microstack/assets/ddl/ops/caddy_access_log_tbl.sql +91 -0
- cancan_microstack/assets/ddl/ops/caddy_certificate_tbl.sql +59 -0
- cancan_microstack/assets/ddl/ops/caddy_rate_limit_tbl.sql +64 -0
- cancan_microstack/assets/ddl/ops/caddy_route_tbl.sql +63 -0
- cancan_microstack/assets/ddl/ops/caddy_stats_tbl.sql +77 -0
- cancan_microstack/assets/ddl/trigger.sql +21 -0
- cancan_microstack/assets/docker/docker-compose.infra.yml +401 -0
- cancan_microstack/assets/scripts/README.md +195 -0
- cancan_microstack/assets/scripts/docker/build_images.sh +44 -0
- cancan_microstack/assets/scripts/docker/force_rebuild_images.sh +38 -0
- cancan_microstack/assets/scripts/docker/rebuild_all.sh +34 -0
- cancan_microstack/assets/scripts/docker/rebuild_compose.sh +61 -0
- cancan_microstack/assets/scripts/docker/restart.sh +35 -0
- cancan_microstack/assets/scripts/docker/restart_compose.sh +35 -0
- cancan_microstack/assets/scripts/docker/start.sh +78 -0
- cancan_microstack/assets/scripts/docker/start_all.sh +46 -0
- cancan_microstack/assets/scripts/docker/start_compose.sh +66 -0
- cancan_microstack/assets/scripts/docker/stop.sh +67 -0
- cancan_microstack/assets/scripts/docker/stop_all.sh +38 -0
- cancan_microstack/assets/scripts/docker/stop_compose.sh +38 -0
- cancan_microstack/assets/scripts/podman/build_images_podman.sh +59 -0
- cancan_microstack/assets/scripts/podman/cleanup_podman.sh +25 -0
- cancan_microstack/assets/scripts/podman/force_rebuild_images_podman.sh +56 -0
- cancan_microstack/assets/scripts/podman/rebuild_all_podman.sh +37 -0
- cancan_microstack/assets/scripts/podman/rebuild_compose_podman.sh +60 -0
- cancan_microstack/assets/scripts/podman/restart_compose_podman.sh +73 -0
- cancan_microstack/assets/scripts/podman/start_all_podman.sh +66 -0
- cancan_microstack/assets/scripts/podman/start_compose_podman.sh +80 -0
- cancan_microstack/assets/scripts/podman/start_podman.sh +91 -0
- cancan_microstack/assets/scripts/podman/stop.sh +73 -0
- cancan_microstack/assets/scripts/podman/stop_all_podman.sh +34 -0
- cancan_microstack/assets/scripts/podman/stop_compose_podman.sh +58 -0
- cancan_microstack/assets/scripts/start_controllersrv.sh +9 -0
- cancan_microstack/assets/scripts/utils/check_all_db_tables.sh +104 -0
- cancan_microstack/assets/scripts/utils/check_env.sh +177 -0
- cancan_microstack/assets/scripts/utils/check_service_management_deployment.sh +225 -0
- cancan_microstack/assets/scripts/utils/deploy_service_management.sh +176 -0
- cancan_microstack/assets/scripts/utils/force_reload_infrasrv.sh +52 -0
- cancan_microstack/assets/scripts/utils/monitor_service_management.sh +187 -0
- cancan_microstack/assets/scripts/utils/reset_postgres_volume.sh +68 -0
- cancan_microstack/assets/scripts/utils/test_async_operations.sh +141 -0
- cancan_microstack/assets/scripts/utils/verify_real_operations.sh +76 -0
- cancan_microstack/assets/service/Dockerfile +65 -0
- cancan_microstack/assets/www/adminops/assets/AppEmpty.vue_vue_type_script_setup_true_lang-BOKUurnM.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-DKV5YOUz.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-Y5bhy7wG.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-8ljYvCW2.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-BWpyqbuQ.css +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-B9Nf1OPl.js +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-DYWZKQ1V.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CA0Jhe78.js +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CCZfTNPF.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BId3kP3M.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BQZTV_Qy.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-BdEYwqFq.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-D-pASR8G.css +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-Byss-yUC.js +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-C3nSZwM7.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-BDI8jLpC.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-DJY4NiF-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-DaUQ4QLw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-w9XCU0UA.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BFzHe6Tw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BJUhWnA-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Cw24WuKp.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Yum47zdB.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-C7ryvbhE.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-Cgd01fUx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-COpG9H0h.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-H_Alq0cf.css +1 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-D0TwMQkA.js +39 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-lqAN6pqM.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-CWlAmzFt.js +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-HoQC1lhx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-BHN1VtX1.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-D3w_lZk8.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-DU_mpIA0.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-i50rZyxN.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-CnHokPL9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-DaZaZpLd.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-B09hK48c.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-wGutKIIU.css +1 -0
- cancan_microstack/assets/www/adminops/assets/caddy-nnCKf8fG.js +1 -0
- cancan_microstack/assets/www/adminops/assets/format-Cuzxgna9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/index-CiFlm8oc.js +64 -0
- cancan_microstack/assets/www/adminops/assets/index-UW0T1Dkc.css +1 -0
- cancan_microstack/assets/www/adminops/assets/service-BYlgGPs_.js +1 -0
- cancan_microstack/assets/www/adminops/assets/service-operation-6GzLw2Z1.js +1 -0
- cancan_microstack/assets/www/adminops/assets/style-CcIXnQ5y.css +1 -0
- cancan_microstack/assets/www/adminops/assets/style-lRnStdGu.js +39 -0
- cancan_microstack/assets/www/adminops/assets/useDebounce-BRlqfXqf.js +1 -0
- cancan_microstack/assets/www/adminops/assets/workflow-CUXs39Ac.js +1 -0
- cancan_microstack/assets/www/adminops/index.html +16 -0
- cancan_microstack/assets/www/adminops/vite.svg +1 -0
- cancan_microstack/cli/__init__.py +14 -0
- cancan_microstack/cli/__main__.py +9 -0
- cancan_microstack/cli/main.py +552 -0
- cancan_microstack/cmd/__init__.py +54 -0
- cancan_microstack/cmd/cancan/__init__.py +12 -0
- cancan_microstack/cmd/cancan/run.py +395 -0
- cancan_microstack/cmd/controllersrv/__init__.py +0 -0
- cancan_microstack/cmd/controllersrv/run.py +131 -0
- cancan_microstack/cmd/infrasrv/__init__.py +5 -0
- cancan_microstack/cmd/infrasrv/run.py +100 -0
- cancan_microstack/cmd/opsbffsrv/__init__.py +5 -0
- cancan_microstack/cmd/opsbffsrv/run.py +96 -0
- cancan_microstack/core/__init__.py +5 -0
- cancan_microstack/core/assets.py +123 -0
- cancan_microstack/core/compose_builder.py +102 -0
- cancan_microstack/core/doctor.py +152 -0
- cancan_microstack/core/microstack.py +71 -0
- cancan_microstack/core/runner.py +56 -0
- cancan_microstack/core/stack_manager.py +186 -0
- cancan_microstack/public/__init__.py +7 -0
- cancan_microstack/public/api/__init__.py +1 -0
- cancan_microstack/public/api/controllersrv_client.py +277 -0
- cancan_microstack/public/api/infrasrv_client.py +404 -0
- cancan_microstack/public/const/__init__.py +1 -0
- cancan_microstack/public/const/action_consts.py +18 -0
- cancan_microstack/public/const/app_consts.py +42 -0
- cancan_microstack/public/const/caddy_consts.py +22 -0
- cancan_microstack/public/const/controllersrv_consts.py +163 -0
- cancan_microstack/public/const/docker_consts.py +15 -0
- cancan_microstack/public/const/error.py +56 -0
- cancan_microstack/public/const/health_consts.py +52 -0
- cancan_microstack/public/const/hook_enums.py +56 -0
- cancan_microstack/public/const/logging_enums.py +13 -0
- cancan_microstack/public/const/metrics_enums.py +36 -0
- cancan_microstack/public/const/monitor_enums.py +26 -0
- cancan_microstack/public/const/operation_consts.py +53 -0
- cancan_microstack/public/const/opsbffsrv_error.py +92 -0
- cancan_microstack/public/const/overrides_consts.py +13 -0
- cancan_microstack/public/const/redis.py +17 -0
- cancan_microstack/public/const/service_consts.py +15 -0
- cancan_microstack/public/const/workflow_consts.py +65 -0
- cancan_microstack/public/error.py +41 -0
- cancan_microstack/public/logging/__init__.py +0 -0
- cancan_microstack/public/logging/initializer.py +109 -0
- cancan_microstack/public/logging/mq_handler.py +279 -0
- cancan_microstack/public/schemas/__init__.py +1 -0
- cancan_microstack/public/schemas/caddy/__init__.py +381 -0
- cancan_microstack/public/schemas/caddy/analysis.py +90 -0
- cancan_microstack/public/schemas/caddy/route.py +18 -0
- cancan_microstack/public/schemas/common.py +79 -0
- cancan_microstack/public/schemas/controllersrv/__init__.py +3 -0
- cancan_microstack/public/schemas/controllersrv/async_requests.py +30 -0
- cancan_microstack/public/schemas/controllersrv/compose_models.py +47 -0
- cancan_microstack/public/schemas/controllersrv/const.py +24 -0
- cancan_microstack/public/schemas/controllersrv/docker_models.py +45 -0
- cancan_microstack/public/schemas/controllersrv/docker_responses.py +104 -0
- cancan_microstack/public/schemas/controllersrv/requests.py +54 -0
- cancan_microstack/public/schemas/controllersrv/responses.py +124 -0
- cancan_microstack/public/schemas/controllersrv/task_models.py +102 -0
- cancan_microstack/public/schemas/controllersrv/validation.py +23 -0
- cancan_microstack/public/schemas/hook_metrics.py +124 -0
- cancan_microstack/public/schemas/hooks.py +39 -0
- cancan_microstack/public/schemas/infra/__init__.py +0 -0
- cancan_microstack/public/schemas/infra/cleanup.py +25 -0
- cancan_microstack/public/schemas/infra/container.py +74 -0
- cancan_microstack/public/schemas/infra/enums.py +135 -0
- cancan_microstack/public/schemas/infra/health_check.py +42 -0
- cancan_microstack/public/schemas/infra/hook_log.py +42 -0
- cancan_microstack/public/schemas/infra/operation.py +90 -0
- cancan_microstack/public/schemas/infra/overview.py +25 -0
- cancan_microstack/public/schemas/infra/push.py +33 -0
- cancan_microstack/public/schemas/infra/service_action_log.py +47 -0
- cancan_microstack/public/schemas/infra/service_config.py +10 -0
- cancan_microstack/public/schemas/infra/service_info.py +69 -0
- cancan_microstack/public/schemas/infra/service_instance.py +93 -0
- cancan_microstack/public/schemas/infra/service_management.py +152 -0
- cancan_microstack/public/schemas/infra/service_operation.py +79 -0
- cancan_microstack/public/schemas/infra/service_registry.py +158 -0
- cancan_microstack/public/schemas/infra/status_types.py +19 -0
- cancan_microstack/public/schemas/infra/workflow.py +566 -0
- cancan_microstack/public/schemas/logging/__init__.py +1 -0
- cancan_microstack/public/schemas/logging/log_event.py +121 -0
- cancan_microstack/public/schemas/opsbffsrv/__init__.py +1 -0
- cancan_microstack/public/schemas/opsbffsrv/async_ops.py +17 -0
- cancan_microstack/public/schemas/opsbffsrv/db_admin.py +147 -0
- cancan_microstack/public/schemas/opsbffsrv/db_init.py +48 -0
- cancan_microstack/public/schemas/opsbffsrv/service_config.py +89 -0
- cancan_microstack/public/schemas/opsbffsrv/service_logs.py +54 -0
- cancan_microstack/public/schemas/service_operation.py +24 -0
- cancan_microstack/public/schemas/service_registry.py +40 -0
- cancan_microstack/public/types/__init__.py +7 -0
- cancan_microstack/public/web/__init__.py +0 -0
- cancan_microstack/public/web/config_value.py +105 -0
- cancan_microstack/public/web/server.py +385 -0
- cancan_microstack/py.typed +0 -0
- cancan_microstack/runtime/__init__.py +0 -0
- cancan_microstack/runtime/compose_cmd.py +228 -0
- cancan_microstack/runtime/host_daemon.py +318 -0
- cancan_microstack/runtime/overrides.py +103 -0
- cancan_microstack/runtime/resources.py +25 -0
- cancan_microstack/runtime/workspace.py +94 -0
- cancan_microstack/services/__init__.py +0 -0
- cancan_microstack/services/controllersrv/__init__.py +8 -0
- cancan_microstack/services/controllersrv/application/__init__.py +0 -0
- cancan_microstack/services/controllersrv/application/docker_compose_app.py +427 -0
- cancan_microstack/services/controllersrv/conf/__init__.py +0 -0
- cancan_microstack/services/controllersrv/conf/config.py +76 -0
- cancan_microstack/services/controllersrv/conf/settings.py +54 -0
- cancan_microstack/services/controllersrv/domain/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/docker_compose_domain.py +278 -0
- cancan_microstack/services/controllersrv/domain/service_validator.py +327 -0
- cancan_microstack/services/controllersrv/domain/task/__init__.py +17 -0
- cancan_microstack/services/controllersrv/domain/task/task_queue.py +286 -0
- cancan_microstack/services/controllersrv/domain/task/task_worker.py +495 -0
- cancan_microstack/services/controllersrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/docker_control_api.py +470 -0
- cancan_microstack/services/controllersrv/router.py +132 -0
- cancan_microstack/services/infrasrv/__init__.py +4 -0
- cancan_microstack/services/infrasrv/application/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/health_check_app.py +24 -0
- cancan_microstack/services/infrasrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/infrasrv/application/logging/log_ingestion_service.py +183 -0
- cancan_microstack/services/infrasrv/application/service_config.py +22 -0
- cancan_microstack/services/infrasrv/application/service_logs_app.py +53 -0
- cancan_microstack/services/infrasrv/application/service_management_app.py +689 -0
- cancan_microstack/services/infrasrv/application/service_operation_tracker.py +251 -0
- cancan_microstack/services/infrasrv/application/service_registry.py +53 -0
- cancan_microstack/services/infrasrv/application/workflow/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_app.py +991 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_queue.py +302 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_tasks.py +46 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_worker_runtime.py +122 -0
- cancan_microstack/services/infrasrv/conf/__init__.py +0 -0
- cancan_microstack/services/infrasrv/conf/config.py +98 -0
- cancan_microstack/services/infrasrv/domain/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/health_check/__init__.py +3 -0
- cancan_microstack/services/infrasrv/domain/health_check/health_check_domain.py +576 -0
- cancan_microstack/services/infrasrv/domain/hooks/__init__.py +19 -0
- cancan_microstack/services/infrasrv/domain/hooks/builtin_hooks.py +308 -0
- cancan_microstack/services/infrasrv/domain/hooks/hook_registry.py +43 -0
- cancan_microstack/services/infrasrv/domain/hooks/hooks_log_utils.py +275 -0
- cancan_microstack/services/infrasrv/domain/hooks/init.py +17 -0
- cancan_microstack/services/infrasrv/domain/hooks/metrics.py +205 -0
- cancan_microstack/services/infrasrv/domain/hooks/pre_registration_hooks.py +490 -0
- cancan_microstack/services/infrasrv/domain/registry/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/registry/service_registry.py +509 -0
- cancan_microstack/services/infrasrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_config/service_config.py +50 -0
- cancan_microstack/services/infrasrv/domain/service_logs/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_logs/service_logs_domain.py +51 -0
- cancan_microstack/services/infrasrv/domain/workflow/__init__.py +4 -0
- cancan_microstack/services/infrasrv/domain/workflow/engine.py +159 -0
- cancan_microstack/services/infrasrv/domain/workflow/node_handlers.py +509 -0
- cancan_microstack/services/infrasrv/domain/workflow/workflow_domain.py +164 -0
- cancan_microstack/services/infrasrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/controllersrv_api.py +165 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/service_registry_cache.py +174 -0
- cancan_microstack/services/infrasrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/execution_log_tbl.py +53 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/node_instance_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_info_tbl.py +59 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_instance_tbl.py +88 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_operation_tbl.py +73 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_version_tbl.py +43 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_engine_alert_tbl.py +57 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_run_tbl.py +56 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_action_log_op.py +239 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config.py +80 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config_manager.py +198 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_info_op.py +297 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_instance_op.py +688 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_operation_op.py +387 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_registry.py +124 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/workflow_op.py +804 -0
- cancan_microstack/services/infrasrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/log_repository.py +129 -0
- cancan_microstack/services/infrasrv/interface/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/health_check_api.py +29 -0
- cancan_microstack/services/infrasrv/interface/api/hooks.py +284 -0
- cancan_microstack/services/infrasrv/interface/api/internal.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/internal_instance_api.py +265 -0
- cancan_microstack/services/infrasrv/interface/api/internal_operation_api.py +206 -0
- cancan_microstack/services/infrasrv/interface/api/service_config.py +50 -0
- cancan_microstack/services/infrasrv/interface/api/service_logs_api.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/service_management_api.py +113 -0
- cancan_microstack/services/infrasrv/interface/api/service_registry.py +117 -0
- cancan_microstack/services/infrasrv/interface/api/workflow_api.py +303 -0
- cancan_microstack/services/infrasrv/interface/schedule/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/schedule/cleanup.py +13 -0
- cancan_microstack/services/infrasrv/interface/schedule/health_check.py +27 -0
- cancan_microstack/services/infrasrv/interface/schedule/log_cleanup.py +26 -0
- cancan_microstack/services/infrasrv/interface/schedule/operation_tracker.py +25 -0
- cancan_microstack/services/infrasrv/interface/schedule/scheduler.py +39 -0
- cancan_microstack/services/infrasrv/interface/schedule/workflow_scheduler.py +115 -0
- cancan_microstack/services/infrasrv/router.py +341 -0
- cancan_microstack/services/opsbffsrv/__init__.py +4 -0
- cancan_microstack/services/opsbffsrv/application/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/async_operation_app.py +150 -0
- cancan_microstack/services/opsbffsrv/application/auth_app.py +285 -0
- cancan_microstack/services/opsbffsrv/application/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_analysis_app.py +344 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_ingestion_service.py +169 -0
- cancan_microstack/services/opsbffsrv/application/caddy/certificate_management_app.py +355 -0
- cancan_microstack/services/opsbffsrv/application/caddy/rate_limit_management_app.py +496 -0
- cancan_microstack/services/opsbffsrv/application/caddy/route_management_app.py +401 -0
- cancan_microstack/services/opsbffsrv/application/caddy/stats_aggregation_app.py +364 -0
- cancan_microstack/services/opsbffsrv/application/db_admin_app.py +103 -0
- cancan_microstack/services/opsbffsrv/application/db_init_app.py +283 -0
- cancan_microstack/services/opsbffsrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/application/logging/log_query_app.py +28 -0
- cancan_microstack/services/opsbffsrv/application/service_config.py +158 -0
- cancan_microstack/services/opsbffsrv/application/service_logs_app.py +74 -0
- cancan_microstack/services/opsbffsrv/application/service_registry.py +36 -0
- cancan_microstack/services/opsbffsrv/application/workflow_ops_app.py +730 -0
- cancan_microstack/services/opsbffsrv/conf/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/conf/config.py +224 -0
- cancan_microstack/services/opsbffsrv/domain/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/admin_init.py +38 -0
- cancan_microstack/services/opsbffsrv/domain/auth/auth_domain.py +108 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/access_log_analysis.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/certificate_management.py +325 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/default_routes.py +53 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/rate_limit_management.py +308 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/route_management.py +279 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/stats_aggregation.py +654 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/db_admin_domain.py +118 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/db_init_domain.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/domain/logging/log_query_domain.py +99 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/service_config.py +81 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/service_registry.py +292 -0
- cancan_microstack/services/opsbffsrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/infrasrv_api.py +242 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/captcha_service.py +67 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/password_service.py +12 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/redis_store.py +131 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/totp_service.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/access_log_parser.py +307 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/admin_api_client.py +678 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/ip_geo_locator.py +176 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/admin_user_tbl.py +33 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_access_log_tbl.py +90 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_certificate_tbl.py +65 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_rate_limit_tbl.py +69 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_route_tbl.py +66 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_stats_tbl.py +78 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_info_tbl.py +51 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_instance_tbl.py +68 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/admin_user_operate.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_access_log.py +531 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_certificate.py +451 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_rate_limit.py +360 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_route.py +271 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_stats.py +343 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_action_log_op.py +57 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_config.py +86 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_info_op.py +79 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_instance.py +58 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_registry.py +138 -0
- cancan_microstack/services/opsbffsrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/log_query_repository.py +87 -0
- cancan_microstack/services/opsbffsrv/interface/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/async_operation_api.py +137 -0
- cancan_microstack/services/opsbffsrv/interface/api/auth_api.py +113 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/access_log_api.py +174 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/certificate_api.py +235 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/rate_limit_api.py +302 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/route_api.py +250 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/stats_api.py +243 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_admin_api.py +62 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_init_api.py +109 -0
- cancan_microstack/services/opsbffsrv/interface/api/instance_management_api.py +165 -0
- cancan_microstack/services/opsbffsrv/interface/api/log_query_api.py +41 -0
- cancan_microstack/services/opsbffsrv/interface/api/mongo_express_proxy_api.py +181 -0
- cancan_microstack/services/opsbffsrv/interface/api/pgweb_proxy_api.py +154 -0
- cancan_microstack/services/opsbffsrv/interface/api/rabbitmq_mgmt_proxy_api.py +518 -0
- cancan_microstack/services/opsbffsrv/interface/api/redis_commander_proxy_api.py +133 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_config.py +146 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_logs_api.py +81 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_registry.py +66 -0
- cancan_microstack/services/opsbffsrv/interface/api/workflow_ops_api.py +413 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/auth_middleware.py +52 -0
- cancan_microstack/services/opsbffsrv/router.py +901 -0
- cancan_microstack/utils/__init__.py +1 -0
- cancan_microstack/utils/container_env.py +218 -0
- cancan_microstack-0.0.1.dist-info/METADATA +155 -0
- cancan_microstack-0.0.1.dist-info/RECORD +440 -0
- cancan_microstack-0.0.1.dist-info/WHEEL +5 -0
- cancan_microstack-0.0.1.dist-info/entry_points.txt +2 -0
- cancan_microstack-0.0.1.dist-info/licenses/LICENSE +21 -0
- cancan_microstack-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""infrasrv startup entrypoint.
|
|
2
|
+
|
|
3
|
+
用于容器内启动 infrasrv 服务(被 cmd/infrasrv/run.py 包装器调用)。
|
|
4
|
+
Start infrasrv inside container (called by workspace cmd wrapper).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
from linglong_web import ServerRouter
|
|
10
|
+
from linglong_web.utils import logger
|
|
11
|
+
|
|
12
|
+
from cancan_microstack.public.web.server import AppServer
|
|
13
|
+
from cancan_microstack.services.infrasrv.application.logging.log_ingestion_service import get_log_ingestion_service
|
|
14
|
+
from cancan_microstack.services.infrasrv.application.workflow.workflow_tasks import register_workflow_tasks
|
|
15
|
+
from cancan_microstack.services.infrasrv.application.workflow.workflow_worker_runtime import (
|
|
16
|
+
start_inline_worker,
|
|
17
|
+
stop_inline_worker,
|
|
18
|
+
)
|
|
19
|
+
from cancan_microstack.services.infrasrv.conf.config import service_conf_dict
|
|
20
|
+
from cancan_microstack.services.infrasrv.domain.hooks import get_hook_manager
|
|
21
|
+
from cancan_microstack.services.infrasrv.domain.hooks.builtin_hooks import register_default_hooks
|
|
22
|
+
from cancan_microstack.services.infrasrv.infrastructure.ddl_manager import init_ddl
|
|
23
|
+
from cancan_microstack.services.infrasrv.interface.schedule.scheduler import scheduler_group
|
|
24
|
+
from cancan_microstack.services.infrasrv.router import router_list
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def initialize_infrasrv() -> None:
|
|
28
|
+
"""infrasrv 的启动回调函数 / Startup callback for infrasrv."""
|
|
29
|
+
|
|
30
|
+
# DDL 自管理:检查并创建 infra 表
|
|
31
|
+
# DDL auto-init: check and create infra tables
|
|
32
|
+
logger.info("Checking and initializing infra tables...")
|
|
33
|
+
ddl_success = await init_ddl()
|
|
34
|
+
if not ddl_success:
|
|
35
|
+
logger.warning("DDL initialization failed, but continuing to start service...")
|
|
36
|
+
|
|
37
|
+
# 初始化预注册钩子
|
|
38
|
+
# Initialize pre-registration hooks
|
|
39
|
+
logger.info("Initializing pre-registration hooks...")
|
|
40
|
+
hook_manager = get_hook_manager()
|
|
41
|
+
register_default_hooks(hook_manager)
|
|
42
|
+
logger.info(f"Registered {len(hook_manager.get_hooks('pre_register'))} pre-registration hooks")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def register_workflow_tasks_on_startup() -> None:
|
|
46
|
+
"""在服务启动阶段注册所有工作流任务 / Register workflow tasks during startup."""
|
|
47
|
+
|
|
48
|
+
register_workflow_tasks()
|
|
49
|
+
start_inline_worker()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def start_log_ingestion() -> None:
|
|
53
|
+
"""启动日志消费服务 / Start log ingestion service."""
|
|
54
|
+
|
|
55
|
+
log_ingestion_service = get_log_ingestion_service()
|
|
56
|
+
await log_ingestion_service.start()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def stop_inline_worker_on_shutdown() -> None:
|
|
60
|
+
"""停止内嵌 worker,确保 infrasrv 退出前清理资源 / Stop inline worker on shutdown."""
|
|
61
|
+
|
|
62
|
+
stop_inline_worker()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def stop_log_ingestion() -> None:
|
|
66
|
+
"""停止日志消费服务 / Stop log ingestion service."""
|
|
67
|
+
|
|
68
|
+
log_ingestion_service = get_log_ingestion_service()
|
|
69
|
+
await log_ingestion_service.shutdown()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def main(host: str = "0.0.0.0", port: int = 8080) -> None:
|
|
73
|
+
"""启动 infrasrv / Start infrasrv."""
|
|
74
|
+
|
|
75
|
+
router_factory = ServerRouter()
|
|
76
|
+
router_factory.initialize(router_list)
|
|
77
|
+
|
|
78
|
+
app = AppServer()
|
|
79
|
+
|
|
80
|
+
await app.initialize(
|
|
81
|
+
service_name="infrasrv",
|
|
82
|
+
router=router_factory.get_router(),
|
|
83
|
+
config_dict=service_conf_dict,
|
|
84
|
+
scheduler_group=scheduler_group,
|
|
85
|
+
on_startup=[
|
|
86
|
+
initialize_infrasrv,
|
|
87
|
+
start_log_ingestion,
|
|
88
|
+
register_workflow_tasks_on_startup,
|
|
89
|
+
],
|
|
90
|
+
on_shutdown=[
|
|
91
|
+
stop_log_ingestion,
|
|
92
|
+
stop_inline_worker_on_shutdown,
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
await app.start(host=host, port=port)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""opsbffsrv startup entrypoint.
|
|
2
|
+
|
|
3
|
+
用于容器内启动 opsbffsrv 服务(被 cmd/opsbffsrv/run.py 包装器调用)。
|
|
4
|
+
Start opsbffsrv inside container (called by workspace cmd wrapper).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
from linglong_web import ServerRouter
|
|
10
|
+
from linglong_web.utils import logger
|
|
11
|
+
|
|
12
|
+
from cancan_microstack.public.web.server import AppServer
|
|
13
|
+
from cancan_microstack.services.opsbffsrv.application.caddy.access_log_ingestion_service import (
|
|
14
|
+
get_caddy_access_log_ingestion_service,
|
|
15
|
+
)
|
|
16
|
+
from cancan_microstack.services.opsbffsrv.application.caddy.route_management_app import RouteManagementApp
|
|
17
|
+
from cancan_microstack.services.opsbffsrv.conf.config import service_conf_dict, validate_auth_config
|
|
18
|
+
from cancan_microstack.services.opsbffsrv.domain.auth.admin_init import ensure_admin_user
|
|
19
|
+
from cancan_microstack.services.opsbffsrv.domain.caddy.default_routes import init_default_routes
|
|
20
|
+
from cancan_microstack.services.opsbffsrv.infrastructure.ddl_manager import init_ddl
|
|
21
|
+
from cancan_microstack.services.opsbffsrv.interface.middleware.auth_middleware import auth_middleware
|
|
22
|
+
from cancan_microstack.services.opsbffsrv.router import router_list
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def initialize_opsbffsrv() -> None:
|
|
26
|
+
"""opsbffsrv 的启动回调函数 / Startup callback for opsbffsrv."""
|
|
27
|
+
|
|
28
|
+
# 认证关键配置 fail-fast 校验(生产缺 Fernet key 直接拒绝启动)。
|
|
29
|
+
# Fail-fast validation of auth-critical config (refuse to start in prod without Fernet key).
|
|
30
|
+
validate_auth_config()
|
|
31
|
+
|
|
32
|
+
# DDL 自管理:检查并创建 ops 表
|
|
33
|
+
# DDL auto-init: check and create ops tables
|
|
34
|
+
logger.info("Checking and initializing ops tables...")
|
|
35
|
+
ddl_success = await init_ddl()
|
|
36
|
+
if not ddl_success:
|
|
37
|
+
logger.warning("DDL initialization failed, but continuing to start service...")
|
|
38
|
+
|
|
39
|
+
# 初始化 admin 用户
|
|
40
|
+
# Initialize admin user
|
|
41
|
+
logger.info("Checking and initializing admin user...")
|
|
42
|
+
await ensure_admin_user()
|
|
43
|
+
|
|
44
|
+
# 初始化默认路由
|
|
45
|
+
# Initialize default routes
|
|
46
|
+
await init_default_routes()
|
|
47
|
+
|
|
48
|
+
# 同步路由到 Caddy
|
|
49
|
+
# Sync routes to Caddy on startup
|
|
50
|
+
logger.info("Syncing routes to Caddy on startup...")
|
|
51
|
+
route_app = RouteManagementApp()
|
|
52
|
+
sync_result = await route_app.sync_all_routes()
|
|
53
|
+
if sync_result.get("status") != "success":
|
|
54
|
+
# Caddy may be intentionally not started in some dev flows.
|
|
55
|
+
# Some environments start opsbffsrv before caddy, so treat this as non-fatal.
|
|
56
|
+
logger.warning("Sync routes to Caddy on startup skipped/failed: %s", sync_result)
|
|
57
|
+
else:
|
|
58
|
+
logger.info("Routes synced to Caddy successfully.")
|
|
59
|
+
|
|
60
|
+
# 启动 Caddy 访问日志采集服务
|
|
61
|
+
# Start Caddy access log ingestion service
|
|
62
|
+
ingestion_service = get_caddy_access_log_ingestion_service()
|
|
63
|
+
await ingestion_service.start()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def shutdown_opsbffsrv() -> None:
|
|
67
|
+
"""opsbffsrv 的关闭回调函数 / Shutdown callback for opsbffsrv."""
|
|
68
|
+
ingestion_service = get_caddy_access_log_ingestion_service()
|
|
69
|
+
await ingestion_service.shutdown()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def main(host: str = "0.0.0.0", port: int = 8080) -> None:
|
|
73
|
+
"""启动 opsbffsrv / Start opsbffsrv."""
|
|
74
|
+
|
|
75
|
+
router_factory = ServerRouter()
|
|
76
|
+
router_factory.initialize(router_list)
|
|
77
|
+
|
|
78
|
+
app = AppServer()
|
|
79
|
+
|
|
80
|
+
await app.initialize(
|
|
81
|
+
service_name="opsbffsrv",
|
|
82
|
+
router=router_factory.get_router(),
|
|
83
|
+
config_dict=service_conf_dict,
|
|
84
|
+
on_startup=[initialize_opsbffsrv],
|
|
85
|
+
on_shutdown=[shutdown_opsbffsrv],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 注册认证中间件
|
|
89
|
+
# Register authentication middleware
|
|
90
|
+
app.app.middleware("http")(auth_middleware)
|
|
91
|
+
|
|
92
|
+
await app.start(host=host, port=port)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Asset loading helpers for Cancan Microstack.
|
|
2
|
+
|
|
3
|
+
资产加载与导出工具,确保包内资源可以安全复制到工作区后再被 Docker 使用。
|
|
4
|
+
Asset loading/export helpers that materialize packaged resources into the caller's workspace
|
|
5
|
+
so Docker (which only sees mounted paths) can read them.
|
|
6
|
+
"""
|
|
7
|
+
import shutil
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from importlib import resources
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import (
|
|
12
|
+
Iterable,
|
|
13
|
+
List,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class AssetRecord:
|
|
19
|
+
"""描述存储在包中的静态资产 / Describe packaged static asset."""
|
|
20
|
+
|
|
21
|
+
logical_name: str
|
|
22
|
+
path: Path
|
|
23
|
+
is_dir: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AssetManager:
|
|
27
|
+
"""资产管理:枚举、导出、解析包内静态文件。
|
|
28
|
+
|
|
29
|
+
Asset management facade to list, export, and resolve packaged static files.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, package: str = "cancan_microstack.assets") -> None:
|
|
33
|
+
self._package = package
|
|
34
|
+
|
|
35
|
+
def list_assets(self, subdir: str | None = None) -> List[AssetRecord]:
|
|
36
|
+
"""列出可用资产(可选限定子目录)/ List available assets (optional subdir filter)."""
|
|
37
|
+
|
|
38
|
+
# 重要:不要用 resources.as_file(...) 的路径来反推 logical_name。
|
|
39
|
+
# 因为当资源来自 zip/轮子时,不同节点可能被物化到不同的临时目录,
|
|
40
|
+
# 这会导致 relative_to 失败,从而丢失前缀(例如返回 "infra" 而不是 "ddl/infra")。
|
|
41
|
+
# Important: do NOT derive logical_name from as_file(...) paths.
|
|
42
|
+
# When packaged (zip/wheel), each node may be materialized into a different temp dir,
|
|
43
|
+
# making relative_to() fail and losing prefixes.
|
|
44
|
+
traversable = resources.files(self._package)
|
|
45
|
+
prefix = ""
|
|
46
|
+
if subdir:
|
|
47
|
+
prefix = "/".join(self._split(subdir))
|
|
48
|
+
traversable = traversable.joinpath(*self._split(subdir))
|
|
49
|
+
records: List[AssetRecord] = []
|
|
50
|
+
|
|
51
|
+
def _walk(node, current_prefix: str) -> None:
|
|
52
|
+
for child in node.iterdir():
|
|
53
|
+
if child.name.startswith("__pycache__") or child.name == ".DS_Store":
|
|
54
|
+
continue
|
|
55
|
+
logical_name = f"{current_prefix}/{child.name}" if current_prefix else child.name
|
|
56
|
+
with resources.as_file(child) as located:
|
|
57
|
+
path = Path(located)
|
|
58
|
+
records.append(
|
|
59
|
+
AssetRecord(
|
|
60
|
+
logical_name=logical_name,
|
|
61
|
+
path=path,
|
|
62
|
+
is_dir=path.is_dir(),
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
if child.is_dir():
|
|
66
|
+
_walk(child, logical_name)
|
|
67
|
+
|
|
68
|
+
_walk(traversable, prefix)
|
|
69
|
+
return records
|
|
70
|
+
|
|
71
|
+
def export_asset(self, logical_name: str, destination: Path, overwrite: bool = False) -> Path:
|
|
72
|
+
"""导出资产到工作区路径;目录用 copytree,文件用 copy2。
|
|
73
|
+
|
|
74
|
+
Export asset into a workspace path so Docker can see it via bind/volume mounts.
|
|
75
|
+
Directories use ``shutil.copytree``; files use ``shutil.copy2``.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
traversable = resources.files(self._package).joinpath(*self._split(logical_name))
|
|
79
|
+
if not traversable.exists(): # pragma: no cover - defensive guard
|
|
80
|
+
raise FileNotFoundError(f"Asset '{logical_name}' not found")
|
|
81
|
+
|
|
82
|
+
with resources.as_file(traversable) as asset_path:
|
|
83
|
+
resolved = Path(asset_path)
|
|
84
|
+
if resolved.is_dir():
|
|
85
|
+
if destination.exists():
|
|
86
|
+
if not overwrite:
|
|
87
|
+
raise FileExistsError(destination)
|
|
88
|
+
shutil.rmtree(destination)
|
|
89
|
+
shutil.copytree(resolved, destination)
|
|
90
|
+
else:
|
|
91
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
if destination.exists() and not overwrite:
|
|
93
|
+
raise FileExistsError(destination)
|
|
94
|
+
shutil.copy2(resolved, destination)
|
|
95
|
+
return destination
|
|
96
|
+
|
|
97
|
+
def read_text(self, logical_name: str) -> str:
|
|
98
|
+
"""读取资产文本 / Read text content from asset."""
|
|
99
|
+
|
|
100
|
+
traversable = resources.files(self._package).joinpath(*self._split(logical_name))
|
|
101
|
+
return traversable.read_text(encoding="utf-8")
|
|
102
|
+
|
|
103
|
+
def resolve_path(self, logical_name: str) -> Path:
|
|
104
|
+
"""将资产实体化为可访问的文件路径 / Materialize and return a filesystem path."""
|
|
105
|
+
|
|
106
|
+
traversable = resources.files(self._package).joinpath(*self._split(logical_name))
|
|
107
|
+
if not traversable.exists():
|
|
108
|
+
raise FileNotFoundError(logical_name)
|
|
109
|
+
with resources.as_file(traversable) as asset_path:
|
|
110
|
+
return Path(asset_path)
|
|
111
|
+
|
|
112
|
+
def _split(self, logical_name: str) -> Iterable[str]:
|
|
113
|
+
return [segment for segment in logical_name.split('/') if segment]
|
|
114
|
+
|
|
115
|
+
def _logical_name(self, path: Path) -> str:
|
|
116
|
+
base = resources.files(self._package)
|
|
117
|
+
with resources.as_file(base) as root_path:
|
|
118
|
+
root = Path(root_path)
|
|
119
|
+
try:
|
|
120
|
+
rel = path.relative_to(root)
|
|
121
|
+
except ValueError: # pragma: no cover - fallback for zipped resources
|
|
122
|
+
return path.name
|
|
123
|
+
return str(rel)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Compose stack builder merging infra assets with service overlays.
|
|
2
|
+
|
|
3
|
+
把内置 infra compose 与业务/覆盖文件进行深度合并,生成最终可运行的栈。
|
|
4
|
+
Deep-merge infra compose with service/override files to produce a runnable stack.
|
|
5
|
+
"""
|
|
6
|
+
import copy
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
Dict,
|
|
11
|
+
Iterable,
|
|
12
|
+
Sequence,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
from .assets import AssetManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ComposeBuilder:
|
|
21
|
+
"""合并内置 infra compose 与工作区覆盖层 / Merge infra compose with workspace overlays."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, asset_manager: AssetManager | None = None,
|
|
24
|
+
base_asset: str = "docker/docker-compose.infra.yml") -> None:
|
|
25
|
+
self.asset_manager = asset_manager or AssetManager()
|
|
26
|
+
self.base_asset = base_asset
|
|
27
|
+
|
|
28
|
+
def build(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
workspace: Path,
|
|
32
|
+
service_file: Path | None = None,
|
|
33
|
+
overrides: Sequence[Path] | None = None,
|
|
34
|
+
output_file: Path | None = None,
|
|
35
|
+
) -> Path:
|
|
36
|
+
"""生成合并后的 docker compose 文件 / Generate merged docker compose file."""
|
|
37
|
+
|
|
38
|
+
workspace = workspace.expanduser().resolve()
|
|
39
|
+
output_path = (output_file or workspace / "compose.cancan.yml").resolve()
|
|
40
|
+
overrides = overrides or []
|
|
41
|
+
|
|
42
|
+
base_model = self._load_asset_yaml(self.base_asset)
|
|
43
|
+
merged = base_model
|
|
44
|
+
if service_file and service_file.exists():
|
|
45
|
+
merged = self._deep_merge(merged, self._load_yaml(service_file))
|
|
46
|
+
|
|
47
|
+
for override in overrides:
|
|
48
|
+
if override.exists():
|
|
49
|
+
merged = self._deep_merge(merged, self._load_yaml(override))
|
|
50
|
+
|
|
51
|
+
# Docker Compose V2 ignores the deprecated `version` field and warns.
|
|
52
|
+
# Docker Compose V2 会忽略已弃用的 `version` 字段并产生警告,因此统一移除。
|
|
53
|
+
merged.pop("version", None)
|
|
54
|
+
|
|
55
|
+
# Avoid network name collisions with pre-existing networks that were not created by Compose.
|
|
56
|
+
# If a network is not marked as external, letting Compose generate a project-scoped name is safer.
|
|
57
|
+
# 避免与历史遗留网络(非 compose 创建,缺少 label)发生冲突。
|
|
58
|
+
# 若网络不是 external,则移除固定 name,让 compose 自动生成项目级网络名。
|
|
59
|
+
networks = merged.get("networks")
|
|
60
|
+
if isinstance(networks, dict):
|
|
61
|
+
for _, net_cfg in networks.items():
|
|
62
|
+
if isinstance(net_cfg, dict) and not net_cfg.get("external"):
|
|
63
|
+
net_cfg.pop("name", None)
|
|
64
|
+
|
|
65
|
+
# When a service has a build definition, prefer local build and do not pull the image.
|
|
66
|
+
# 对于带 build 的服务,默认使用本地构建,避免先去 pull 同名镜像。
|
|
67
|
+
services = merged.get("services")
|
|
68
|
+
if isinstance(services, dict):
|
|
69
|
+
for _, svc_cfg in services.items():
|
|
70
|
+
if isinstance(svc_cfg, dict) and "build" in svc_cfg:
|
|
71
|
+
svc_cfg.setdefault("pull_policy", "never")
|
|
72
|
+
|
|
73
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
with output_path.open("w", encoding="utf-8") as fp:
|
|
75
|
+
yaml.safe_dump(merged, fp, sort_keys=False)
|
|
76
|
+
return output_path
|
|
77
|
+
|
|
78
|
+
def _load_yaml(self, path: Path) -> Dict[str, Any]:
|
|
79
|
+
with path.open("r", encoding="utf-8") as fp:
|
|
80
|
+
data = yaml.safe_load(fp) or {}
|
|
81
|
+
if not isinstance(data, dict): # pragma: no cover - defensive
|
|
82
|
+
raise ValueError(f"Compose file must be a mapping: {path}")
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
def _load_asset_yaml(self, logical_name: str) -> Dict[str, Any]:
|
|
86
|
+
asset_path = self.asset_manager.resolve_path(logical_name)
|
|
87
|
+
return self._load_yaml(asset_path)
|
|
88
|
+
|
|
89
|
+
def _deep_merge(self, base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
|
|
90
|
+
result = copy.deepcopy(base)
|
|
91
|
+
for key, value in override.items():
|
|
92
|
+
if key not in result:
|
|
93
|
+
result[key] = copy.deepcopy(value)
|
|
94
|
+
continue
|
|
95
|
+
if isinstance(result[key], dict) and isinstance(value, dict):
|
|
96
|
+
result[key] = self._deep_merge(result[key], value)
|
|
97
|
+
else:
|
|
98
|
+
result[key] = copy.deepcopy(value)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def collect_override_paths(self, raw_values: Iterable[str]) -> Sequence[Path]:
|
|
102
|
+
return [Path(item).expanduser().resolve() for item in raw_values]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""`cancan doctor` — pre-flight environment & configuration checks.
|
|
2
|
+
|
|
3
|
+
在 `cancan stack up` 之前一次性诊断常见的"起不来"原因:容器引擎、端口占用、
|
|
4
|
+
工作区识别、运行时资产是否就绪、生产弱默认值。
|
|
5
|
+
|
|
6
|
+
Diagnose the common "stack won't come up" causes before `cancan stack up`:
|
|
7
|
+
container engine, port conflicts, workspace detection, bootstrapped runtime
|
|
8
|
+
assets, and weak production defaults. Stdlib-only.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import socket
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from ..runtime.compose_cmd import detect_compose_command
|
|
17
|
+
from ..runtime.workspace import detect_workspace_root
|
|
18
|
+
|
|
19
|
+
OK = "ok"
|
|
20
|
+
WARN = "warn"
|
|
21
|
+
FAIL = "fail"
|
|
22
|
+
|
|
23
|
+
_GLYPH = {OK: "✓", WARN: "!", FAIL: "✗"}
|
|
24
|
+
|
|
25
|
+
# Host-published ports declared in the bundled infra compose.
|
|
26
|
+
_HOST_PORTS = {
|
|
27
|
+
8080: "caddy (local dev gateway)",
|
|
28
|
+
22100: "controllersrv (host daemon)",
|
|
29
|
+
25432: "postgres",
|
|
30
|
+
26379: "redis",
|
|
31
|
+
27017: "mongo",
|
|
32
|
+
35672: "rabbitmq amqp",
|
|
33
|
+
35673: "rabbitmq management",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Weak local-dev defaults that must not survive into production.
|
|
37
|
+
_WEAK_DEFAULTS = {
|
|
38
|
+
"POSTGRES_PASSWORD": "postgres123",
|
|
39
|
+
"RABBITMQ_PASSWORD": "admin123",
|
|
40
|
+
"MONGO_INITDB_ROOT_PASSWORD": "admin123",
|
|
41
|
+
"MONGO_EXPRESS_PASSWORD": "admin123",
|
|
42
|
+
"RABBITMQ_MGMT_PASSWORD": "admin123",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Check:
|
|
48
|
+
status: str
|
|
49
|
+
title: str
|
|
50
|
+
detail: str = ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _port_in_use(port: int, host: str = "127.0.0.1") -> bool:
|
|
54
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
55
|
+
sock.settimeout(0.3)
|
|
56
|
+
return sock.connect_ex((host, port)) == 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _parse_env_file(path: Path) -> dict[str, str]:
|
|
60
|
+
values: dict[str, str] = {}
|
|
61
|
+
try:
|
|
62
|
+
for raw in path.read_text(encoding="utf-8").splitlines():
|
|
63
|
+
line = raw.strip()
|
|
64
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
65
|
+
continue
|
|
66
|
+
k, _, v = line.partition("=")
|
|
67
|
+
values[k.strip()] = v.strip()
|
|
68
|
+
except OSError:
|
|
69
|
+
pass
|
|
70
|
+
return values
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def collect_checks(workspace: Path) -> list[Check]:
|
|
74
|
+
checks: list[Check] = []
|
|
75
|
+
|
|
76
|
+
# 1) container engine + daemon
|
|
77
|
+
try:
|
|
78
|
+
cmd = detect_compose_command()
|
|
79
|
+
checks.append(Check(OK, "Container engine", " ".join(cmd.argv_prefix)))
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
checks.append(Check(FAIL, "Container engine", f"no usable docker/podman compose: {exc}"))
|
|
82
|
+
|
|
83
|
+
# 2) host port availability
|
|
84
|
+
busy = [f"{p} ({_HOST_PORTS[p]})" for p in sorted(_HOST_PORTS) if _port_in_use(p)]
|
|
85
|
+
if busy:
|
|
86
|
+
checks.append(Check(WARN, "Host ports", "in use (a previous stack? other apps?): " + ", ".join(busy)))
|
|
87
|
+
else:
|
|
88
|
+
checks.append(Check(OK, "Host ports", "all stack ports free"))
|
|
89
|
+
|
|
90
|
+
# 3) workspace detection
|
|
91
|
+
try:
|
|
92
|
+
root = detect_workspace_root(start=workspace)
|
|
93
|
+
checks.append(Check(OK, "Workspace root", str(root)))
|
|
94
|
+
except Exception:
|
|
95
|
+
checks.append(Check(WARN, "Workspace root", f"no workspace marker found under {workspace}; run `cancan init`"))
|
|
96
|
+
|
|
97
|
+
# 4) bootstrapped runtime assets
|
|
98
|
+
expected = {
|
|
99
|
+
".env": workspace / ".env",
|
|
100
|
+
"ddl/create_db.sql": workspace / "ddl" / "create_db.sql",
|
|
101
|
+
"builds/caddy": workspace / "builds" / "caddy",
|
|
102
|
+
"builds/service/Dockerfile": workspace / "builds" / "service" / "Dockerfile",
|
|
103
|
+
}
|
|
104
|
+
missing = [name for name, p in expected.items() if not p.exists()]
|
|
105
|
+
if missing:
|
|
106
|
+
checks.append(Check(WARN, "Runtime assets", "missing (run `cancan stack up --bootstrap` or `cancan init`): " + ", ".join(missing)))
|
|
107
|
+
else:
|
|
108
|
+
checks.append(Check(OK, "Runtime assets", "bootstrapped"))
|
|
109
|
+
|
|
110
|
+
# 5) weak defaults / required secrets in .env
|
|
111
|
+
env_path = workspace / ".env"
|
|
112
|
+
if env_path.exists():
|
|
113
|
+
env = _parse_env_file(env_path)
|
|
114
|
+
weak = [k for k, default in _WEAK_DEFAULTS.items() if env.get(k, default) == default]
|
|
115
|
+
if not env.get("AUTH_TOTP_FERNET_KEY"):
|
|
116
|
+
checks.append(Check(FAIL, "AUTH_TOTP_FERNET_KEY", "empty in .env — opsbffsrv will refuse to start in prod mode"))
|
|
117
|
+
else:
|
|
118
|
+
checks.append(Check(OK, "AUTH_TOTP_FERNET_KEY", "set"))
|
|
119
|
+
if weak:
|
|
120
|
+
checks.append(Check(WARN, "Weak default secrets", "still default (CHANGE FOR PRODUCTION): " + ", ".join(weak)))
|
|
121
|
+
else:
|
|
122
|
+
checks.append(Check(OK, "Secrets", "no known weak defaults in .env"))
|
|
123
|
+
else:
|
|
124
|
+
checks.append(Check(WARN, "Config (.env)", "not found; `cancan init` / `stack up --bootstrap` will create it"))
|
|
125
|
+
|
|
126
|
+
return checks
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def run_doctor(workspace: Path) -> int:
|
|
130
|
+
"""运行诊断并打印结果,返回退出码(有 FAIL=2,仅 WARN=0)。
|
|
131
|
+
Run checks, print a report, return an exit code (FAIL → 2, otherwise 0).
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
checks = collect_checks(workspace)
|
|
135
|
+
print("cancan doctor — environment check\n")
|
|
136
|
+
for c in checks:
|
|
137
|
+
line = f" [{_GLYPH[c.status]}] {c.title}"
|
|
138
|
+
if c.detail:
|
|
139
|
+
line += f": {c.detail}"
|
|
140
|
+
print(line)
|
|
141
|
+
|
|
142
|
+
fails = sum(1 for c in checks if c.status == FAIL)
|
|
143
|
+
warns = sum(1 for c in checks if c.status == WARN)
|
|
144
|
+
print()
|
|
145
|
+
if fails:
|
|
146
|
+
print(f"✗ {fails} blocking issue(s), {warns} warning(s). Fix the ✗ items before `cancan stack up`.")
|
|
147
|
+
return 2
|
|
148
|
+
if warns:
|
|
149
|
+
print(f"! {warns} warning(s). The stack can start, but review the ! items (especially before production).")
|
|
150
|
+
return 0
|
|
151
|
+
print("✓ All checks passed.")
|
|
152
|
+
return 0
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""High level orchestration helpers."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
from .assets import AssetManager
|
|
6
|
+
from .compose_builder import ComposeBuilder
|
|
7
|
+
from .runner import ServiceRunner
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CancanMicrostack:
|
|
11
|
+
"""Convenience facade bundling assets, compose builder, and service runner."""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self.assets = AssetManager()
|
|
15
|
+
self.compose_builder = ComposeBuilder(self.assets)
|
|
16
|
+
self.runner = ServiceRunner()
|
|
17
|
+
|
|
18
|
+
def build_compose(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
workspace: Path,
|
|
22
|
+
service_file: Path | None = None,
|
|
23
|
+
overrides: Sequence[Path] | None = None,
|
|
24
|
+
output: Path | None = None,
|
|
25
|
+
) -> Path:
|
|
26
|
+
self._log(
|
|
27
|
+
"Building Cancan compose file",
|
|
28
|
+
workspace=workspace,
|
|
29
|
+
service_file=service_file,
|
|
30
|
+
overrides=overrides,
|
|
31
|
+
output=output,
|
|
32
|
+
)
|
|
33
|
+
return self.compose_builder.build(
|
|
34
|
+
workspace=workspace,
|
|
35
|
+
service_file=service_file,
|
|
36
|
+
overrides=overrides,
|
|
37
|
+
output_file=output,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def export_asset(self, logical_name: str, destination: Path, overwrite: bool = False) -> Path:
|
|
41
|
+
self._log("Exporting asset", logical_name=logical_name, destination=destination)
|
|
42
|
+
return self.assets.export_asset(logical_name, destination, overwrite)
|
|
43
|
+
|
|
44
|
+
def run_service(
|
|
45
|
+
self,
|
|
46
|
+
service_name: str,
|
|
47
|
+
host: str = "0.0.0.0",
|
|
48
|
+
port: int = 8080,
|
|
49
|
+
workspace: Path | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
self._log(
|
|
52
|
+
"Starting managed service",
|
|
53
|
+
service=service_name,
|
|
54
|
+
host=host,
|
|
55
|
+
port=port,
|
|
56
|
+
workspace=workspace,
|
|
57
|
+
)
|
|
58
|
+
self.runner.run(service_name, host=host, port=port, workspace=workspace)
|
|
59
|
+
|
|
60
|
+
async def run_service_async(
|
|
61
|
+
self,
|
|
62
|
+
service_name: str,
|
|
63
|
+
host: str = "0.0.0.0",
|
|
64
|
+
port: int = 8080,
|
|
65
|
+
workspace: Path | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
await self.runner.run_async(service_name, host=host, port=port, workspace=workspace)
|
|
68
|
+
|
|
69
|
+
def _log(self, message: str, **fields) -> None:
|
|
70
|
+
extras = " ".join(f"{k}={v}" for k, v in fields.items() if v is not None)
|
|
71
|
+
print(f"[cancan] {message} {extras}".strip())
|