Commit Graph

10 Commits (21130c6f1e675264efd18f461d1e8f0670cba3dd)

Author SHA1 Message Date
mirivlad f892d377a0 debug: add log.Printf in CallPluginFunction Go binding to trace calls 2026-06-08 12:26:43 +08:00
mirivlad d6e28d7b1f fix(plugins): restore plugin runtimes on startup + graceful shutdown
Root cause: initVault() called Discover() but never called
SyncConfig(), InitRuntimes(), CallInitHooks(), or StartSchedulers().
On restart, plugins were discovered from disk but their config state
(Installed/Enabled) was not restored and no Lua VMs were created.

This caused:
- Settings → Plugins showing plugins as 'not installed' after restart
- Sidebar showing calendar item (from enabledSet config) but
  GetPluginPanelHTML failing (only checked p.Active)
- Toggle working in-session but state lost on restart
- closeVault() not stopping plugin schedulers/shutdown/VM (leak)

Fixes:
- bindings_config.go: add SyncConfig(appCfg) after Discover()
- bindings_config.go: add InitRuntimes() + CallInitHooks() + StartSchedulers()
  after watcher start
- bindings_config.go: add StopSchedulers + CallShutdownHooks + CloseRuntimes
  to closeVault() (before db.Close since plugins use DB)
- bindings_plugins.go: GetPluginPanelHTML now checks enabledSet || p.Active,
  consistent with ListSystemViewsWithPlugins and ListPlugins
2026-06-08 11:45:35 +08:00
mirivlad 3b79754f45 fix: rollback Enabled on activation failure + fatal on_init + rollback test
1. SetPluginEnabled(true): after DeactivatePlugin, also call Disable(name)
   to rollback in-memory Enabled state (not just config).
2. on_init failure is now fatal for ActivatePlugin — returns error
   and rolls back scheduler + VM (was incorrectly non-fatal).
3. TestSetPluginEnabled_BrokenPlugin_Rollback: end-to-end test with
   broken plugin (invalid interval), verifies error + not Active +
   not Enabled + not in config.
2026-06-08 00:14:49 +08:00
mirivlad 45cfe1b0a6 fix: финальный cleanup Lua plugin lifecycle
1. ActivatePlugin → error return:
   - Возвращает ошибки при создании VM, загрузке main.lua, scheduler setup
   - on_init failure = non-fatal (logged, activation continues)
   - SetPluginEnabled сохраняет EnabledPlugins в config ТОЛЬКО после успешной активации
   - При ошибке активации — rollback (deactivate + не сохраняем в config)

2. CallPluginFunction fully thread-safe:
   - Новый метод LuaVM.CallFunctionJSON(segments, paramsJSON)
   - JSON→Lua conversion происходит под vm.mu (внутри lock)
   - Убраны parseParamsToLua/goToLua из bindings_plugins.go
   - goToLua перенесён в runtime.go (под lock)

3. PluginPage → CalendarPluginPage:
   - Компонент явно календарный (get-events/create-event/update-event/delete-event)
   - Переименован для ясности
   - Console log префиксы обновлены

4. Тесты:
   - TestSetPluginEnabled_ActivateFails_NoConfigSave: проверяет что при ошибке
     активации плагин НЕ сохраняется в EnabledPlugins
   - TestActivatePlugin_ErrorReturn: проверяет все режимы ошибок
   - TestCallFunctionJSON_ThreadSafe: JSON object/array/empty params
   - TestDeactivatePlugin_Idempotent: двойная деактивация = no-op
   - TestInitRuntimes_SkipsDisabled: только Enabled плагины активируются
2026-06-07 22:58:26 +08:00
mirivlad d83c8c80e1 fix: второй стабилизационный проход Lua plugin lifecycle
1. Enabled/Active state separation:
   - Enable() sets Enabled=true (persisted in config), does NOT create runtime
   - ActivatePlugin() checks Enabled && !Active, creates VM + scheduler
   - DeactivatePlugin() stops runtime, keeps Enabled=true
   - InitRuntimes() iterates Enabled plugins, sets Active=true after creation
   - SyncConfig() restores Enabled from config, does NOT touch Active

2. ActivatePlugin: добавлен vm.SetServices(m.Services)

3. Discover: атомарная замена списка (newPlugins slice), нет дублирования

4. CallPluginFunction: thread-safe через LuaVM.CallFunction (vm.mu + callWithTimeout)

5. Uninstall активного плагина: полная деактивация (StopScheduler → on_shutdown → CloseVM → Active=false)

6. GetPluginPanelHTML: валидация panel path (no absolute, no .., must be .html, must be within plugin dir)

7. PluginPage: убран hardcoded 'calendar-plugin', используется funcPrefix из pluginName

Тесты:
- security_test.go: +8 тестов (FullLifecycle, ActivatePlugin_Services, Discover_Idempotent,
  ReloadPlugins_NoDuplicates, CallPluginFunction_Timeout, Uninstall_ActivePlugin,
  GetPluginPanelHTML_PathTraversal, FullLifecycle_EndToEnd)
- manager_test.go: обновлены тесты под новую семантику Enabled/Active
2026-06-07 20:49:43 +08:00
mirivlad 4df83cd361 security: стабилизационный аудит Lua plugin system
Исправления:
- Install: идемпотентность (no duplicates in InstalledPlugins)
- ReloadPlugins: StopSchedulers + CallShutdownHooks перед CloseRuntimes
- StopSchedulers: обнуление scheduler=nil после остановки
- Scheduler.Stop: обнуление tasks после wg.Wait
- Lua sandbox: блокировка package.loadlib/seeall/preload/loaders/loaded/path/cpath/config/searchpath
- Lua sandbox: блокировка load (глобальная функция)
- CallPluginFunction: валидация funcName (regex [a-zA-Z_][a-zA-Z0-9_]*, max 3 segments)
- CallPluginFunction: убрана строковая сборка Lua-кодa, вызов через PCall напрямую
- PluginPage.svelte: проверка e.source === iframeEl.contentWindow
- PluginPage.svelte: type checking для msg.source, msg.action

Тесты:
- security_test.go: 18 новых тестов (sandbox, lifecycle, validation)
- Все существующие тесты проходят

Документация:
- docs/plugins-security.md: модель безопасности, sandbox, протокол, lifecycle
2026-06-07 19:19:44 +08:00
mirivlad a1d7c7b88b feat: PluginPage iframe bridge + CallPluginFunction binding
- PluginPage.svelte: bidirectional postMessage bridge with iframe
  - Handles: ready, get-events, create-event, update-event, delete-event
  - Queues messages until iframe is ready
  - Exports handleDrop() for drag-and-drop from parent
- CallPluginFunction binding: calls arbitrary Lua functions on active plugins
  - Supports dotted paths: 'calendar.create_event' → _G.calendar.create_event
  - JSON params → Lua table conversion
- LuaVM: added DoString(), LState(), VM() public methods
- Plugin: added VM() getter for external access
2026-06-07 16:37:32 +08:00
mirivlad 308772dee8 fix: simplify ReloadPlugins - remove redundant deactivate loop
- SyncConfig already sets Active correctly from config
- InitRuntimes only processes Active plugins
- No need for separate deactivate pass
2026-06-07 16:01:28 +08:00
mirivlad e99ff984b1 feat: plugin install/uninstall lifecycle + UI buttons
- AppConfig: add InstalledPlugins []string
- Manager.Discover(): no config dependency, all plugins start inactive
- Manager.SyncConfig(): apply installed/enabled state from AppConfig
- Manager.Enable(): works for plugins without on_install hook
- Manager.Install/Uninstall(): run on_install/on_uninstall hooks
- ActivatePlugin: skip if HasInstall && !Installed
- ReloadPlugins: Discover → SyncConfig → InitRuntimes
- Bindings: InstallPlugin, UninstallPlugin
- SettingsPlugins: install/uninstall buttons, toggle only after install
- Calendar: migration moved from on_init to on_install, on_uninstall drops tables
- Tests: all 12 pass (manager + runtime + calendar)
2026-06-07 15:28:37 +08:00
mirivlad b80941f908 feat: плагин-система Lua + Calendar reference plugin
- Lua VM runtime: gopher-lua с песочницей, хуки on_init/on_tick/on_shutdown
- API: verstak.node.* / verstak.db.* / verstak.config.* / verstak.state.*
- API: verstak.worklog.* / verstak.activity.* / verstak.file.*
- API: verstak.schedule.* / verstak.http.* / verstak.ui.*
- Менеджер плагинов: жизненный цикл, инициализация, шаблоны
- Scheduler: фоновые задачи с интервалами
- PluginPage.svelte: контейнер для iframe-панелей плагинов
- Calendar plugin: миграция, категории CRUD, события CRUD
- Calendar: расширенный рекарренс (daily/weekly/monthly/yearly)
- Calendar: связь с узлами Верстака, напоминания, HTTP-праздники
- Calendar: Lua-тест-сьют (15 тестов), Go-интеграционный тест
- fix: query_row использует реальные Column() вместо guessColumns
2026-06-07 14:59:46 +08:00