From 3a6a571361cd76260b2982a8c6cb1d50b7dac13a Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Thu, 2 Apr 2026 13:38:38 -0300 Subject: [PATCH] chore: sync workspace state --- .gitignore | 1 + .playwright-mcp/cristo-snapshot.md | 37 + .../page-2026-03-31T19-16-51-748Z.yml | 45 + .../page-2026-03-31T19-17-49-094Z.yml | 25 + .../page-2026-03-31T19-18-09-643Z.yml | 111 + .../page-2026-03-31T19-18-17-256Z.yml | 39 + .../page-2026-03-31T19-18-59-132Z.yml | 109 + .../page-2026-03-31T19-19-05-563Z.yml | 50 + .../page-2026-03-31T19-19-18-155Z.yml | 123 ++ .../page-2026-03-31T19-21-55-814Z.yml | 111 + .../page-2026-03-31T19-22-10-960Z.yml | 61 + .../page-2026-03-31T19-22-29-106Z.yml | 82 + .../page-2026-04-02T11-04-19-343Z.yml | 27 + .../page-2026-04-02T11-05-27-254Z.yml | 147 ++ .../suggestions-container-check.json | 16 + .playwright-mcp/suggestions-css-rules.json | 64 + .playwright-mcp/suggestions-dom-check.json | 112 + AGENTS.md | 68 + Cargo.lock | 2 +- Cargo.toml | 1 + backup-to-s3/.env.example | 7 + backup-to-s3/Cargo.lock | 1818 +++++++++++++++++ backup-to-s3/Cargo.toml | 15 + backup-to-s3/src/main.rs | 213 ++ botserver | 2 +- default-vault.tar | 0 migrations.tar.gz | Bin 71693 -> 0 bytes ping_google.cmd | 5 + prompts/c1.md | 110 +- prompts/c3.md | 461 +++++ prompts/container.md | 29 +- prompts/fail2ban-start.sh | 11 - prompts/go.md | 15 - prompts/migrate.md | 18 - prompts/win.md | 244 --- prompts/workflow.md | 242 --- restart.sh | 23 +- usekb2.md | 154 ++ 38 files changed, 3978 insertions(+), 620 deletions(-) create mode 100644 .playwright-mcp/cristo-snapshot.md create mode 100644 .playwright-mcp/page-2026-03-31T19-16-51-748Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-17-49-094Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-18-09-643Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-18-17-256Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-18-59-132Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-19-05-563Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-19-18-155Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-21-55-814Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-22-10-960Z.yml create mode 100644 .playwright-mcp/page-2026-03-31T19-22-29-106Z.yml create mode 100644 .playwright-mcp/page-2026-04-02T11-04-19-343Z.yml create mode 100644 .playwright-mcp/page-2026-04-02T11-05-27-254Z.yml create mode 100644 .playwright-mcp/suggestions-container-check.json create mode 100644 .playwright-mcp/suggestions-css-rules.json create mode 100644 .playwright-mcp/suggestions-dom-check.json create mode 100644 backup-to-s3/.env.example create mode 100644 backup-to-s3/Cargo.lock create mode 100644 backup-to-s3/Cargo.toml create mode 100644 backup-to-s3/src/main.rs delete mode 100644 default-vault.tar delete mode 100644 migrations.tar.gz create mode 100644 ping_google.cmd create mode 100644 prompts/c3.md delete mode 100644 prompts/fail2ban-start.sh delete mode 100644 prompts/go.md delete mode 100644 prompts/migrate.md delete mode 100644 prompts/win.md delete mode 100644 prompts/workflow.md create mode 100644 usekb2.md diff --git a/.gitignore b/.gitignore index 8502f08..54d8759 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ errors_utf8.txt vault-unseal-keysdefault-vault.tar prompts/sec-bots.md +AGENTS-PROD.md diff --git a/.playwright-mcp/cristo-snapshot.md b/.playwright-mcp/cristo-snapshot.md new file mode 100644 index 0000000..18ca4a1 --- /dev/null +++ b/.playwright-mcp/cristo-snapshot.md @@ -0,0 +1,37 @@ +- generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e7]: Chat + - generic [ref=e8]: + - button [ref=e9] [cursor=pointer]: + - img [ref=e10] + - button [ref=e11] [cursor=pointer]: + - img [ref=e12] + - button [ref=e14] [cursor=pointer]: + - img [ref=e15] + - generic [ref=e18]: + - generic [ref=e19]: + - main [ref=e20]: + - paragraph [ref=e31]: Olá! Sou o assistente virtual do Santuário Cristo Redentor. Como posso ajudá-lo hoje com informações sobre celebrações, eventos, visitação ou orações? + - contentinfo [ref=e21]: + - generic [ref=e22]: + - button "Agendar Batizado" [ref=e32] [cursor=pointer] + - button "Agendar Casamento" [ref=e33] [cursor=pointer] + - button "Agendar Missa" [ref=e34] [cursor=pointer] + - button "Agendar Peregrinação" [ref=e35] [cursor=pointer] + - button "Pedido de Oração" [ref=e36] [cursor=pointer] + - button "Uso de Imagem" [ref=e37] [cursor=pointer] + - button "Licenciamento" [ref=e38] [cursor=pointer] + - button "Evento/Iluminação" [ref=e39] [cursor=pointer] + - button "Cadastrar Guia" [ref=e40] [cursor=pointer] + - button "Fazer Doação" [ref=e41] [cursor=pointer] + - generic [ref=e23]: + - generic [ref=e24]: + - button "Agent" [ref=e25] [cursor=pointer] + - button "Chat" [ref=e26] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e27] + - button "↑" [ref=e28] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-16-51-748Z.yml b/.playwright-mcp/page-2026-03-31T19-16-51-748Z.yml new file mode 100644 index 0000000..8cff2dd --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-16-51-748Z.yml @@ -0,0 +1,45 @@ +- generic [ref=e2]: + - generic [ref=e3]: + - img "General Bots" [ref=e6] + - heading "Welcome Back" [level=1] [ref=e7] + - paragraph [ref=e8]: Sign in to your General Bots account + - generic [ref=e10]: + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: Email Address + - generic [ref=e14]: + - img + - textbox "Email Address" [ref=e15]: + - /placeholder: you@example.com + - generic [ref=e16]: + - generic [ref=e17]: Password + - generic [ref=e18]: + - img + - textbox "Password" [ref=e19]: + - /placeholder: •••••••• + - button [ref=e20] [cursor=pointer]: + - img [ref=e21] + - generic [ref=e24]: + - generic [ref=e27] [cursor=pointer]: Remember me + - link "Forgot password?" [ref=e28] [cursor=pointer]: + - /url: /auth/forgot-password + - button "Sign In" [ref=e29] [cursor=pointer]: + - generic [ref=e30]: Sign In + - generic [ref=e32]: or continue with + - generic [ref=e33]: + - button "Google" [ref=e34] [cursor=pointer]: + - img [ref=e35] + - text: Google + - button "Microsoft" [ref=e40] [cursor=pointer]: + - img [ref=e41] + - text: Microsoft + - button "GitHub" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - text: GitHub + - button "Apple" [ref=e49] [cursor=pointer]: + - img [ref=e50] + - text: Apple + - paragraph [ref=e53]: + - text: Don't have an account? + - link "Create account" [ref=e54] [cursor=pointer]: + - /url: /auth/register \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-17-49-094Z.yml b/.playwright-mcp/page-2026-03-31T19-17-49-094Z.yml new file mode 100644 index 0000000..cca8113 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-17-49-094Z.yml @@ -0,0 +1,25 @@ +- generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e7]: Chat + - generic [ref=e8]: + - button [ref=e9] [cursor=pointer]: + - img [ref=e10] + - button [ref=e11] [cursor=pointer]: + - img [ref=e12] + - button [ref=e14] [cursor=pointer]: + - img [ref=e15] + - generic [ref=e18]: + - generic [ref=e19]: + - main [ref=e20] + - contentinfo [ref=e21]: + - generic [ref=e23]: + - generic [ref=e24]: + - button "Agent" [ref=e25] [cursor=pointer] + - button "Chat" [ref=e26] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e27] + - button "↑" [ref=e28] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-18-09-643Z.yml b/.playwright-mcp/page-2026-03-31T19-18-09-643Z.yml new file mode 100644 index 0000000..a9803f8 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-18-09-643Z.yml @@ -0,0 +1,111 @@ +- generic [ref=e2]: + - complementary [ref=e3]: + - generic "Home" [ref=e4] [cursor=pointer]: + - img [ref=e5] + - generic "Search" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - generic "Terminal" [ref=e12] [cursor=pointer]: + - img [ref=e13] + - generic "User" [ref=e15] [cursor=pointer]: + - img [ref=e16] + - generic "Apps" [ref=e19] [cursor=pointer]: + - img [ref=e20] + - generic "Settings" [ref=e25] [cursor=pointer]: + - img [ref=e26] + - generic [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - generic [ref=e32] [cursor=pointer]: + - img [ref=e34] + - generic [ref=e36]: Vibe + - generic [ref=e37] [cursor=pointer]: + - img [ref=e39] + - generic [ref=e44]: CRM + - generic [ref=e45] [cursor=pointer]: + - img [ref=e47] + - generic [ref=e49]: Campaigns + - generic [ref=e50] [cursor=pointer]: + - img [ref=e52] + - generic [ref=e53]: Lists + - generic [ref=e54] [cursor=pointer]: + - img [ref=e56] + - generic [ref=e58]: Templates + - generic [ref=e59] [cursor=pointer]: + - img [ref=e61] + - generic [ref=e64]: Tasks + - generic [ref=e65] [cursor=pointer]: + - img [ref=e67] + - generic [ref=e69]: Chat + - generic [ref=e70] [cursor=pointer]: + - img [ref=e72] + - generic [ref=e74]: Terminal + - generic [ref=e75] [cursor=pointer]: + - img [ref=e77] + - generic [ref=e79]: Explorer + - generic [ref=e80] [cursor=pointer]: + - img [ref=e82] + - generic [ref=e85]: Editor + - generic [ref=e86] [cursor=pointer]: + - img [ref=e88] + - generic [ref=e93]: Designer + - generic [ref=e94] [cursor=pointer]: + - img [ref=e96] + - generic [ref=e99]: BASIC + - generic [ref=e100] [cursor=pointer]: + - img [ref=e102] + - generic [ref=e105]: Browser + - generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - generic: + - generic: Connecting... + - main [ref=e121] + - contentinfo [ref=e122]: + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" + - contentinfo [ref=e130]: + - img [ref=e134] [cursor=pointer] + - generic [ref=e136]: + - combobox [ref=e138] [cursor=pointer]: + - option "🎨 Default" + - option "☀️ Light" [selected] + - option "🍊 Orange" + - option "🌃 Cyberpunk" + - option "🌴 Retrowave" + - option "💭 Vapor Dream" + - option "✨ Y2K" + - option "🔲 3D Bevel" + - option "🕹️ Arcade" + - option "🪩 Disco" + - option "🎸 Grunge" + - option "🎺 Jazz" + - option "🌻 Mellow" + - option "🏠 Mid Century" + - option "📷 Polaroid" + - option "📺 Cartoons" + - option "🏖️ Seaside" + - option "⌨️ Typewriter" + - option "📠 Xerox" + - option "📁 XTree" + - button "Sign In" [ref=e139] [cursor=pointer] + - generic [ref=e140]: + - generic [ref=e141]: 00:00 + - generic [ref=e142]: 01/01/2026 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-18-17-256Z.yml b/.playwright-mcp/page-2026-03-31T19-18-17-256Z.yml new file mode 100644 index 0000000..4f5d8b6 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-18-17-256Z.yml @@ -0,0 +1,39 @@ +- generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121]: + - paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje? + - generic [ref=e158]: 🔍 Detectar Desvios na Folha + - contentinfo [ref=e122]: + - generic [ref=e123]: + - button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e156] [cursor=pointer] + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-18-59-132Z.yml b/.playwright-mcp/page-2026-03-31T19-18-59-132Z.yml new file mode 100644 index 0000000..83cdb5d --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-18-59-132Z.yml @@ -0,0 +1,109 @@ +- generic [ref=e2]: + - complementary [ref=e3]: + - generic "Home" [ref=e4] [cursor=pointer]: + - img [ref=e5] + - generic "Search" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - generic "Terminal" [ref=e12] [cursor=pointer]: + - img [ref=e13] + - generic "User" [ref=e15] [cursor=pointer]: + - img [ref=e16] + - generic "Apps" [ref=e19] [cursor=pointer]: + - img [ref=e20] + - generic "Settings" [ref=e25] [cursor=pointer]: + - img [ref=e26] + - generic [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - generic [ref=e32] [cursor=pointer]: + - img [ref=e34] + - generic [ref=e36]: Vibe + - generic [ref=e37] [cursor=pointer]: + - img [ref=e39] + - generic [ref=e44]: CRM + - generic [ref=e45] [cursor=pointer]: + - img [ref=e47] + - generic [ref=e49]: Campaigns + - generic [ref=e50] [cursor=pointer]: + - img [ref=e52] + - generic [ref=e53]: Lists + - generic [ref=e54] [cursor=pointer]: + - img [ref=e56] + - generic [ref=e58]: Templates + - generic [ref=e59] [cursor=pointer]: + - img [ref=e61] + - generic [ref=e64]: Tasks + - generic [ref=e65] [cursor=pointer]: + - img [ref=e67] + - generic [ref=e69]: Chat + - generic [ref=e70] [cursor=pointer]: + - img [ref=e72] + - generic [ref=e74]: Terminal + - generic [ref=e75] [cursor=pointer]: + - img [ref=e77] + - generic [ref=e79]: Explorer + - generic [ref=e80] [cursor=pointer]: + - img [ref=e82] + - generic [ref=e85]: Editor + - generic [ref=e86] [cursor=pointer]: + - img [ref=e88] + - generic [ref=e93]: Designer + - generic [ref=e94] [cursor=pointer]: + - img [ref=e96] + - generic [ref=e99]: BASIC + - generic [ref=e100] [cursor=pointer]: + - img [ref=e102] + - generic [ref=e105]: Browser + - generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121] + - contentinfo [ref=e122]: + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" + - contentinfo [ref=e130]: + - img [ref=e134] [cursor=pointer] + - generic [ref=e136]: + - combobox [ref=e138] [cursor=pointer]: + - option "🎨 Default" + - option "☀️ Light" [selected] + - option "🍊 Orange" + - option "🌃 Cyberpunk" + - option "🌴 Retrowave" + - option "💭 Vapor Dream" + - option "✨ Y2K" + - option "🔲 3D Bevel" + - option "🕹️ Arcade" + - option "🪩 Disco" + - option "🎸 Grunge" + - option "🎺 Jazz" + - option "🌻 Mellow" + - option "🏠 Mid Century" + - option "📷 Polaroid" + - option "📺 Cartoons" + - option "🏖️ Seaside" + - option "⌨️ Typewriter" + - option "📠 Xerox" + - option "📁 XTree" + - button "Sign In" [ref=e139] [cursor=pointer] + - generic [ref=e140]: + - generic [ref=e141]: 00:00 + - generic [ref=e142]: 01/01/2026 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-19-05-563Z.yml b/.playwright-mcp/page-2026-03-31T19-19-05-563Z.yml new file mode 100644 index 0000000..5c215d1 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-19-05-563Z.yml @@ -0,0 +1,50 @@ +- generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121]: + - paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje? + - generic [ref=e169]: 🔍 Detectar Desvios na Folha + - contentinfo [ref=e122]: + - generic [ref=e123]: + - button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e156] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e167] [cursor=pointer] + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-19-18-155Z.yml b/.playwright-mcp/page-2026-03-31T19-19-18-155Z.yml new file mode 100644 index 0000000..056eba5 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-19-18-155Z.yml @@ -0,0 +1,123 @@ +- generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121]: + - paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje? + - generic [ref=e169]: 🔍 Detectar Desvios na Folha + - generic [ref=e171]: + - paragraph [ref=e172]: + - text: 🕵️‍♀️ + - strong [ref=e173]: Passo a passo para detectar desvios na sua folha de pagamento + - list [ref=e174]: + - listitem [ref=e175]: + - paragraph [ref=e176]: + - strong [ref=e177]: Acesse o Contracheque Online + - list [ref=e178]: + - listitem [ref=e179]: Entre no portal SIPES/SEAD com seu login. + - listitem [ref=e180]: Visualize o contracheque do mês corrente e dos últimos 6 meses. + - listitem [ref=e181]: + - paragraph [ref=e182]: + - strong [ref=e183]: Compare os valores + - list [ref=e184]: + - listitem [ref=e185]: + - text: 📈 + - strong [ref=e186]: Remuneração Base + - text: ": deve permanecer constante (exceto progressões ou promoções)." + - listitem [ref=e187]: + - text: ➕ + - strong [ref=e188]: Verbas Variáveis + - text: ": identifique adicionais, horas extras ou gratificações que não reconhece." + - listitem [ref=e189]: + - text: ➖ + - strong [ref=e190]: Descontos + - text: ": cheque descontos de INSS, IRRF, pensão alimentícia e demais deduções." + - listitem [ref=e191]: + - paragraph [ref=e192]: + - strong [ref=e193]: Solicite documentos complementares (se necessário) + - list [ref=e194]: + - listitem [ref=e195]: + - strong [ref=e196]: Histórico de contracheques + - text: – tipo + - code [ref=e197]: CONTRACHEQUE_HISTORICO + - text: . + - listitem [ref=e198]: + - strong [ref=e199]: Informe de rendimentos + - text: ou + - strong [ref=e200]: Ficha financeira + - text: – para cruzar com a Receita Federal. + - listitem [ref=e201]: + - paragraph [ref=e202]: + - strong [ref=e203]: Cruzamento de informações + - list [ref=e204]: + - listitem [ref=e205]: + - text: Use o + - strong [ref=e206]: Informe de Rendimentos + - text: para verificar se os valores declarados coincidem com o que consta na folha. + - listitem [ref=e207]: + - text: Compare a + - strong [ref=e208]: Ficha Financeira + - text: com o extrato bancário (se houver depósito direto). + - listitem [ref=e209]: + - paragraph [ref=e210]: + - strong [ref=e211]: Registre um protocolo na SEAD/SEPLAG‑SE + - list [ref=e212]: + - listitem [ref=e213]: + - text: "Caso encontre alguma divergência, abra um protocolo (ex.:" + - code [ref=e214]: CCH123456 + - text: ). + - listitem [ref=e215]: Descreva claramente a diferença encontrada e anexe prints ou documentos. + - listitem [ref=e216]: + - paragraph [ref=e217]: + - strong [ref=e218]: Acompanhamento + - list [ref=e219]: + - listitem [ref=e220]: Acompanhe o status do protocolo pelo portal ou pelo telefone da Ouvidoria. + - listitem [ref=e221]: Se a resposta for insatisfatória, solicite revisão ou escalonamento. + - separator [ref=e222] + - paragraph [ref=e223]: + - text: ⚙️ + - strong [ref=e224]: Posso ajudar a solicitar algum desses documentos + - text: (histórico de contracheques, informe de rendimentos, ficha financeira) ou a abrir um protocolo de auditoria. Basta informar os dados solicitados (CPF, matrícula, e‑mail, etc.). + - contentinfo [ref=e122]: + - generic [ref=e123]: + - button "📄 Segunda Via de Contracheque" [ref=e225] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e226] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e227] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e228] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e229] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e230] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e231] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e232] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e233] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e234] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e235] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e236] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e237] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e238] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e239] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e240] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e241] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e242] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e243] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e244] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e245] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e246] [cursor=pointer] + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-21-55-814Z.yml b/.playwright-mcp/page-2026-03-31T19-21-55-814Z.yml new file mode 100644 index 0000000..a9803f8 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-21-55-814Z.yml @@ -0,0 +1,111 @@ +- generic [ref=e2]: + - complementary [ref=e3]: + - generic "Home" [ref=e4] [cursor=pointer]: + - img [ref=e5] + - generic "Search" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - generic "Terminal" [ref=e12] [cursor=pointer]: + - img [ref=e13] + - generic "User" [ref=e15] [cursor=pointer]: + - img [ref=e16] + - generic "Apps" [ref=e19] [cursor=pointer]: + - img [ref=e20] + - generic "Settings" [ref=e25] [cursor=pointer]: + - img [ref=e26] + - generic [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - generic [ref=e32] [cursor=pointer]: + - img [ref=e34] + - generic [ref=e36]: Vibe + - generic [ref=e37] [cursor=pointer]: + - img [ref=e39] + - generic [ref=e44]: CRM + - generic [ref=e45] [cursor=pointer]: + - img [ref=e47] + - generic [ref=e49]: Campaigns + - generic [ref=e50] [cursor=pointer]: + - img [ref=e52] + - generic [ref=e53]: Lists + - generic [ref=e54] [cursor=pointer]: + - img [ref=e56] + - generic [ref=e58]: Templates + - generic [ref=e59] [cursor=pointer]: + - img [ref=e61] + - generic [ref=e64]: Tasks + - generic [ref=e65] [cursor=pointer]: + - img [ref=e67] + - generic [ref=e69]: Chat + - generic [ref=e70] [cursor=pointer]: + - img [ref=e72] + - generic [ref=e74]: Terminal + - generic [ref=e75] [cursor=pointer]: + - img [ref=e77] + - generic [ref=e79]: Explorer + - generic [ref=e80] [cursor=pointer]: + - img [ref=e82] + - generic [ref=e85]: Editor + - generic [ref=e86] [cursor=pointer]: + - img [ref=e88] + - generic [ref=e93]: Designer + - generic [ref=e94] [cursor=pointer]: + - img [ref=e96] + - generic [ref=e99]: BASIC + - generic [ref=e100] [cursor=pointer]: + - img [ref=e102] + - generic [ref=e105]: Browser + - generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - generic: + - generic: Connecting... + - main [ref=e121] + - contentinfo [ref=e122]: + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" + - contentinfo [ref=e130]: + - img [ref=e134] [cursor=pointer] + - generic [ref=e136]: + - combobox [ref=e138] [cursor=pointer]: + - option "🎨 Default" + - option "☀️ Light" [selected] + - option "🍊 Orange" + - option "🌃 Cyberpunk" + - option "🌴 Retrowave" + - option "💭 Vapor Dream" + - option "✨ Y2K" + - option "🔲 3D Bevel" + - option "🕹️ Arcade" + - option "🪩 Disco" + - option "🎸 Grunge" + - option "🎺 Jazz" + - option "🌻 Mellow" + - option "🏠 Mid Century" + - option "📷 Polaroid" + - option "📺 Cartoons" + - option "🏖️ Seaside" + - option "⌨️ Typewriter" + - option "📠 Xerox" + - option "📁 XTree" + - button "Sign In" [ref=e139] [cursor=pointer] + - generic [ref=e140]: + - generic [ref=e141]: 00:00 + - generic [ref=e142]: 01/01/2026 \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-22-10-960Z.yml b/.playwright-mcp/page-2026-03-31T19-22-10-960Z.yml new file mode 100644 index 0000000..423c196 --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-22-10-960Z.yml @@ -0,0 +1,61 @@ +- generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121]: + - paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje? + - generic [ref=e180]: 🔍 Detectar Desvios na Folha + - contentinfo [ref=e122]: + - generic [ref=e123]: + - button "📄 Segunda Via de Contracheque" [ref=e146] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e147] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e148] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e149] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e150] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e151] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e152] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e153] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e154] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [active] [ref=e155] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e156] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e157] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e158] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e159] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e160] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e161] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e162] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e163] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e164] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e165] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e166] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e167] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e168] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e169] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e170] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e171] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e172] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e173] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e174] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e175] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e176] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e177] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e178] [cursor=pointer] + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-31T19-22-29-106Z.yml b/.playwright-mcp/page-2026-03-31T19-22-29-106Z.yml new file mode 100644 index 0000000..dd83b4e --- /dev/null +++ b/.playwright-mcp/page-2026-03-31T19-22-29-106Z.yml @@ -0,0 +1,82 @@ +- generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - main [ref=e121]: + - paragraph [ref=e145]: Olá! Sou o Assistente Virtual da SEAD — Secretaria de Estado da Administração de Sergipe. Estou aqui para ajudá-lo com contracheques, agendamentos no CEAC, perícias médicas, documentos funcionais, cursos da Escola de Governo e muito mais. Como posso te ajudar hoje? + - generic [ref=e180]: 🔍 Detectar Desvios na Folha + - generic [ref=e182]: + - paragraph [ref=e183]: + - text: 🔎 + - strong [ref=e184]: Vamos analisar a sua folha de pagamento para identificar possíveis desvios. + - text: Para isso preciso que você me informe os dados cadastrais do servidor (mesmo que já os tenha usado antes). Assim consigo solicitar o histórico de contracheques e compará‑los. + - blockquote [ref=e185]: + - paragraph [ref=e186]: + - strong [ref=e187]: Dados necessários + - list [ref=e188]: + - listitem [ref=e189]: CPF (com ou sem formatação) + - listitem [ref=e190]: Matrícula funcional + - listitem [ref=e191]: Nome completo do servidor + - listitem [ref=e192]: "Órgão de lotação (ex.: SEAD, SEED, SES, SSP)" + - listitem [ref=e193]: E‑mail (para envio do documento) + - listitem [ref=e194]: Telefone com DDD + - paragraph [ref=e195]: + - text: ⚠️ + - strong [ref=e196]: Confirme + - text: que está de acordo em fornecer essas informações e eu solicito o documento + - code [ref=e197]: CONTRACHEQUE_HISTORICO + - text: . Depois analisaremos juntos as variações encontradas. + - contentinfo [ref=e122]: + - generic [ref=e123]: + - button "📄 Segunda Via de Contracheque" [ref=e198] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e199] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e200] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e201] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e202] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e203] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e204] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e205] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e206] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e207] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e208] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e209] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e210] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e211] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e212] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e213] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e214] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e215] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e216] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e217] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e218] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e219] [cursor=pointer] + - button "📄 Segunda Via de Contracheque" [ref=e220] [cursor=pointer] + - button "📅 Agendar Atendimento no CEAC" [ref=e221] [cursor=pointer] + - button "🩺 Agendar Perícia Médica" [ref=e222] [cursor=pointer] + - button "📋 Solicitar Documentos Funcionais" [ref=e223] [cursor=pointer] + - button "✏️ Atualizar Dados Cadastrais" [ref=e224] [cursor=pointer] + - button "🎓 Cursos da Escola de Governo" [ref=e225] [cursor=pointer] + - button "💰 Informações sobre Benefícios" [ref=e226] [cursor=pointer] + - button "🔍 Consultar Protocolo" [ref=e227] [cursor=pointer] + - button "📞 Falar com a SEAD" [ref=e228] [cursor=pointer] + - button "🔍 Detectar Desvios na Folha" [ref=e229] [cursor=pointer] + - button "❓ Outras Solicitações" [ref=e230] [cursor=pointer] + - generic [ref=e124]: + - generic [ref=e125]: + - button "Agent" [ref=e126] [cursor=pointer] + - button "Chat" [ref=e127] [cursor=pointer] + - textbox "Message... (type @ to mention)" [ref=e128] + - button "↑" [ref=e129] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-02T11-04-19-343Z.yml b/.playwright-mcp/page-2026-04-02T11-04-19-343Z.yml new file mode 100644 index 0000000..cbbe70c --- /dev/null +++ b/.playwright-mcp/page-2026-04-02T11-04-19-343Z.yml @@ -0,0 +1,27 @@ +- generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e7]: Chat + - generic [ref=e8]: + - button [ref=e9] [cursor=pointer]: + - img [ref=e10] + - button [ref=e11] [cursor=pointer]: + - img [ref=e12] + - button [ref=e14] [cursor=pointer]: + - img [ref=e15] + - generic [ref=e18]: + - generic [ref=e19]: + - generic: + - generic: Connecting... + - main [ref=e20] + - contentinfo [ref=e21]: + - generic [ref=e23]: + - generic [ref=e24]: + - button "Agent" [ref=e25] [cursor=pointer] + - button "Chat" [ref=e26] [cursor=pointer] + - textbox "Message... (type @ to mention)" [active] [ref=e27] + - button "↑" [ref=e28] [cursor=pointer] + - button "Scroll to bottom": + - img + - generic: + - generic: + - button "View" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-04-02T11-05-27-254Z.yml b/.playwright-mcp/page-2026-04-02T11-05-27-254Z.yml new file mode 100644 index 0000000..f197640 --- /dev/null +++ b/.playwright-mcp/page-2026-04-02T11-05-27-254Z.yml @@ -0,0 +1,147 @@ +- generic [ref=e2]: + - complementary [ref=e3]: + - generic "Home" [ref=e4] [cursor=pointer]: + - img [ref=e5] + - generic "Search" [ref=e8] [cursor=pointer]: + - img [ref=e9] + - generic "Terminal" [ref=e12] [cursor=pointer]: + - img [ref=e13] + - generic "User" [ref=e15] [cursor=pointer]: + - img [ref=e16] + - generic "Apps" [ref=e19] [cursor=pointer]: + - img [ref=e20] + - generic "Settings" [ref=e25] [cursor=pointer]: + - img [ref=e26] + - generic [ref=e29]: + - generic [ref=e30]: + - generic [ref=e31]: + - generic [ref=e32] [cursor=pointer]: + - img [ref=e34] + - generic [ref=e36]: Vibe + - generic [ref=e37] [cursor=pointer]: + - img [ref=e39] + - generic [ref=e44]: CRM + - generic [ref=e45] [cursor=pointer]: + - img [ref=e47] + - generic [ref=e49]: Campaigns + - generic [ref=e50] [cursor=pointer]: + - img [ref=e52] + - generic [ref=e53]: Lists + - generic [ref=e54] [cursor=pointer]: + - img [ref=e56] + - generic [ref=e58]: Templates + - generic [ref=e59] [cursor=pointer]: + - img [ref=e61] + - generic [ref=e64]: Tasks + - generic [ref=e65] [cursor=pointer]: + - img [ref=e67] + - generic [ref=e69]: Chat + - generic [ref=e70] [cursor=pointer]: + - img [ref=e72] + - generic [ref=e74]: Terminal + - generic [ref=e75] [cursor=pointer]: + - img [ref=e77] + - generic [ref=e79]: Explorer + - generic [ref=e80] [cursor=pointer]: + - img [ref=e82] + - generic [ref=e85]: Editor + - generic [ref=e86] [cursor=pointer]: + - img [ref=e88] + - generic [ref=e93]: Designer + - generic [ref=e94] [cursor=pointer]: + - img [ref=e96] + - generic [ref=e99]: BASIC + - generic [ref=e100] [cursor=pointer]: + - img [ref=e102] + - generic [ref=e105]: Browser + - generic [ref=e106]: + - generic [ref=e107]: + - generic [ref=e108]: Chat + - generic [ref=e109]: + - button [ref=e110] [cursor=pointer]: + - img [ref=e111] + - button [ref=e112] [cursor=pointer]: + - img [ref=e113] + - button [ref=e115] [cursor=pointer]: + - img [ref=e116] + - generic [ref=e119]: + - generic [ref=e120]: + - complementary [ref=e121]: + - button "Chat" [ref=e122]: + - img [ref=e123] + - button "Tasks" [ref=e125]: + - img [ref=e126] + - button "Terminal" [ref=e129]: + - img [ref=e130] + - button "Explorer" [ref=e132]: + - img [ref=e133] + - button "Editor" [ref=e135]: + - img [ref=e136] + - button "Browser" [ref=e139]: + - img [ref=e140] + - main + - generic [ref=e143]: + - generic [ref=e144]: + - text: // BROWSER + - textbox "No preview active" [ref=e145] + - generic [ref=e147]: Waiting for app preview... + - generic [ref=e149]: // TERMINAL + - generic [ref=e150]: + - generic [ref=e151]: "Agent #1" + - text: EVOLVED + - generic [ref=e152]: Claude Opus 4.5 — 99% + - generic [ref=e153]: + - generic [ref=e154]: + - text: Plan + - button [ref=e155] + - generic [ref=e156]: + - text: YOLO + - button [ref=e157] + - generic [ref=e158]: + - generic [ref=e159]: + - button "◄" [ref=e160] + - text: 0 / 0 + - button "►" [ref=e161] + - generic [ref=e162]: + - button "💬" [ref=e163] + - button "✏️" [ref=e164] + - button "" [ref=e165] + - contentinfo [ref=e166]: + - generic [ref=e168]: Reference Entity + - generic [ref=e169]: + - generic [ref=e170]: + - button "Agent" [ref=e171] + - button "Chat" [ref=e172] + - textbox "Message... (type @ to mention)" [active] [ref=e173] + - button "↑" [ref=e174] + - button "Scroll to bottom" [ref=e175]: + - img [ref=e176] + - button "View" [ref=e180] + - contentinfo [ref=e181]: + - img [ref=e185] [cursor=pointer] + - generic [ref=e187]: + - combobox [ref=e189] [cursor=pointer]: + - option "🎨 Default" + - option "☀️ Light" + - option "🍊 Orange" + - option "🌃 Cyberpunk" + - option "🌴 Retrowave" + - option "💭 Vapor Dream" + - option "✨ Y2K" + - option "🔲 3D Bevel" + - option "🕹️ Arcade" + - option "🪩 Disco" + - option "🎸 Grunge" + - option "🎺 Jazz" + - option "🌻 Mellow" + - option "🏠 Mid Century" + - option "📷 Polaroid" + - option "📺 Cartoons" + - option "🏖️ Seaside" + - option "⌨️ Typewriter" [selected] + - option "📠 Xerox" + - option "📁 XTree" + - button "Sign In" [ref=e190] [cursor=pointer] + - generic [ref=e191]: + - generic [ref=e192]: 00:00 + - generic [ref=e193]: 01/01/2026 \ No newline at end of file diff --git a/.playwright-mcp/suggestions-container-check.json b/.playwright-mcp/suggestions-container-check.json new file mode 100644 index 0000000..f63228e --- /dev/null +++ b/.playwright-mcp/suggestions-container-check.json @@ -0,0 +1,16 @@ +{ + "found": true, + "tag": "DIV", + "className": "suggestions-container", + "innerHTML": "", + "childCount": 0, + "display": "flex", + "visibility": "visible", + "opacity": "1", + "height": "4px", + "maxHeight": "100px", + "overflow": "auto", + "hasClass": false, + "parent": "FOOTER ", + "styleAttr": null +} \ No newline at end of file diff --git a/.playwright-mcp/suggestions-css-rules.json b/.playwright-mcp/suggestions-css-rules.json new file mode 100644 index 0000000..7d91360 --- /dev/null +++ b/.playwright-mcp/suggestions-css-rules.json @@ -0,0 +1,64 @@ +{ + "rules": [ + { + "sheet": "https://chat.pragmatismo.com.br/suite/css/app.css", + "selector": "*", + "display": "", + "maxHeight": "", + "overflow": "", + "opacity": "", + "visibility": "", + "height": "", + "padding": "0px", + "margin": "0px" + }, + { + "sheet": "https://chat.pragmatismo.com.br/suite/css/app.css", + "selector": "*", + "display": "", + "maxHeight": "", + "overflow": "", + "opacity": "", + "visibility": "", + "height": "", + "padding": "", + "margin": "" + }, + { + "sheet": "https://chat.pragmatismo.com.br/suite/css/base.css", + "selector": "*", + "display": "", + "maxHeight": "", + "overflow": "", + "opacity": "", + "visibility": "", + "height": "", + "padding": "0px", + "margin": "0px" + }, + { + "sheet": "inline", + "selector": "*", + "display": "", + "maxHeight": "", + "overflow": "", + "opacity": "", + "visibility": "", + "height": "", + "padding": "0px", + "margin": "0px" + }, + { + "sheet": "https://chat.pragmatismo.com.br/suite/chat/chat.css?v=3", + "selector": ".suggestions-container", + "display": "flex", + "maxHeight": "100px", + "overflow": "", + "opacity": "", + "visibility": "", + "height": "", + "padding": "", + "margin": "" + } + ] +} \ No newline at end of file diff --git a/.playwright-mcp/suggestions-dom-check.json b/.playwright-mcp/suggestions-dom-check.json new file mode 100644 index 0000000..297c28f --- /dev/null +++ b/.playwright-mcp/suggestions-dom-check.json @@ -0,0 +1,112 @@ +[ + { + "tag": "DIV", + "className": "suggestions-container has-bot-suggestions", + "id": "suggestions", + "text": "Agendar BatizadoAgendar CasamentoAgendar MissaAgendar PeregrinaçãoPedido de OraçãoUso de ImagemLicen", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "FOOTER." + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Agendar Batizado", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Agendar Casamento", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Agendar Missa", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Agendar Peregrinação", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Pedido de Oração", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Uso de Imagem", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Licenciamento", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Evento/Iluminação", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Cadastrar Guia", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + }, + { + "tag": "BUTTON", + "className": "suggestion-chip", + "id": "", + "text": "Fazer Doação", + "display": "flex", + "visibility": "visible", + "hidden": false, + "parent": "DIV.suggestions-container has-bot-suggestions" + } +] \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 2f347cc..d4399a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -246,6 +246,7 @@ match x { ## ❌ Absolute Prohibitions - NEVER search /target folder! It is binary compiled. +- ❌ **NEVER** hardcode passwords, tokens, API keys, or any credentials in source code — ALWAYS use `generate_random_string()` or environment variables - ❌ **NEVER** build in release mode - ONLY debug builds allowed - ❌ **NEVER** use `--release` flag on ANY cargo command - ❌ **NEVER** run `cargo build` - use `cargo check` for syntax verification @@ -860,3 +861,70 @@ Continue on gb/ workspace. Follow AGENTS.md strictly: - **VERIFY LAST** - Only compile/diagnostics after ALL fixes - **DELETE DEAD CODE** - Don't keep unused code around - **GIT WORKFLOW** - ALWAYS push to ALL repositories (github, pragmatismo) + +--- + +## Deploy in Prod Workflow + +### CI/CD Pipeline (Primary Method) + +1. **Push to ALM** — triggers CI/CD automatically: + ```bash + cd botserver + git push alm main + git push origin main + cd .. + git add botserver + git commit -m "Update botserver: " + git push alm main + git push origin main + ``` + +2. **Wait for CI** — build takes ~3-4 minutes. Check status: + ```bash + # Via web: https://alm.pragmatismo.com.br/GeneralBots/botserver/actions + # Or check binary timestamp after ~4 min sleep: + sleep 240 + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \ + "sudo incus exec system -- stat -c '%y' /opt/gbo/bin/botserver" + ``` + +3. **Restart in prod** — after binary updates: + ```bash + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \ + "sudo incus exec system -- pkill -f botserver || true" + sleep 2 + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \ + "sudo incus exec system -- bash -c 'cd /opt/gbo/bin && sudo -u gbuser RUST_LOG=info ./botserver --noconsole > /opt/gbo/logs/botserver-output.log 2>&1 &'" + ``` + +4. **Verify deployment**: + ```bash + # Wait for bootstrap (~2 min) + sleep 120 + # Check health + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \ + "sudo incus exec system -- curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health" + # Check logs + ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 administrator@prod-host \ + "sudo incus exec system -- tail -30 /opt/gbo/logs/botserver-output.log" + ``` + +### Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| `valkey-cli ping` hangs indefinitely | Valkey requires password auth | Install `nc` or `ss` in container for health checks | +| `nc: command not found` | Prod container lacks netcat | `sudo incus exec system -- apt-get install -y netcat-openbsd` | +| Cache connection timeout | iptables DROP rule on port 6379 | `sudo incus exec system -- iptables -I INPUT -i lo -j ACCEPT` | +| `AUTH failed` on Valkey | Valkey runs without password but Vault has one | Code tries no-password URL first, then with password | +| `Cannot start a runtime from within a runtime` | `block_on()` called from async context | Use `.await` directly, never `runtime.block_on()` in async functions | +| Secret not found in Vault | Path mismatch between seeding and reading | Seeding: `secret/gbo/cache`, Reading: `gbo/cache` (kv2 prepends `secret/`) | +| CI completed but binary not updated | Deploy step fails silently (SSH/transfer issue) | Build locally and transfer, or re-push to trigger CI again | + +### Critical Paths in Vault + +- **Seeding writes to**: `secret/gbo/{service}` (e.g., `secret/gbo/cache`) +- **Code reads via**: `SecretPaths::{SERVICE}` which maps to `gbo/{service}` +- **kv2::read** prepends `secret/` automatically and looks up `secret/data/gbo/{service}` +- **All paths must match**: `gbo/cache`, `gbo/drive`, `gbo/tables`, `gbo/directory`, `gbo/llm`, `gbo/meet`, `gbo/alm`, `gbo/vectordb`, `gbo/encryption`, `gbo/email` diff --git a/Cargo.lock b/Cargo.lock index 14ffca5..faeb9bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,7 +1275,7 @@ dependencies = [ [[package]] name = "botapp" -version = "6.3.0" +version = "6.1.0" dependencies = [ "anyhow", "botlib", diff --git a/Cargo.toml b/Cargo.toml index c591623..f27ff45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "bottest", "botui", ] +exclude = ["backup-to-s3"] [workspace.lints.rust] diff --git a/backup-to-s3/.env.example b/backup-to-s3/.env.example new file mode 100644 index 0000000..a90d8ad --- /dev/null +++ b/backup-to-s3/.env.example @@ -0,0 +1,7 @@ +AWS_ACCESS_KEY_ID=your_access_key +AWS_SECRET_ACCESS_KEY=your_secret_key +AWS_REGION=us-east-1 +S3_BUCKET=prod-gbo-backup +INCUS_PROJECT=PROD-GBO1 +CONTAINER_NAME= +KEEP_COUNT=10 diff --git a/backup-to-s3/Cargo.lock b/backup-to-s3/Cargo.lock new file mode 100644 index 0000000..128beda --- /dev/null +++ b/backup-to-s3/Cargo.lock @@ -0,0 +1,1818 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backup-to-s3" +version = "0.1.0" +dependencies = [ + "chrono", + "dotenv", + "rusoto_core", + "rusoto_credential", + "rusoto_s3", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rusoto_core" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" +dependencies = [ + "async-trait", + "base64", + "bytes", + "crc32fast", + "futures", + "http", + "hyper", + "hyper-tls", + "lazy_static", + "log", + "rusoto_credential", + "rusoto_signature", + "rustc_version", + "serde", + "serde_json", + "tokio", + "xml-rs", +] + +[[package]] +name = "rusoto_credential" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" +dependencies = [ + "async-trait", + "chrono", + "dirs-next", + "futures", + "hyper", + "serde", + "serde_json", + "shlex", + "tokio", + "zeroize", +] + +[[package]] +name = "rusoto_s3" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aae4677183411f6b0b412d66194ef5403293917d66e70ab118f07cc24c5b14d" +dependencies = [ + "async-trait", + "bytes", + "futures", + "rusoto_core", + "xml-rs", +] + +[[package]] +name = "rusoto_signature" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" +dependencies = [ + "base64", + "bytes", + "chrono", + "digest", + "futures", + "hex", + "hmac", + "http", + "hyper", + "log", + "md-5", + "percent-encoding", + "pin-project-lite", + "rusoto_credential", + "rustc_version", + "serde", + "sha2", + "tokio", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/backup-to-s3/Cargo.toml b/backup-to-s3/Cargo.toml new file mode 100644 index 0000000..30b5114 --- /dev/null +++ b/backup-to-s3/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "backup-to-s3" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +rusoto_s3 = "0.48" +rusoto_core = "0.48" +rusoto_credential = "0.48" +dotenv = "0.15" +chrono = "0.4" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-appender = "0.2" diff --git a/backup-to-s3/src/main.rs b/backup-to-s3/src/main.rs new file mode 100644 index 0000000..46d6540 --- /dev/null +++ b/backup-to-s3/src/main.rs @@ -0,0 +1,213 @@ +use chrono::Utc; +use rusoto_core::Region; +use rusoto_credential::StaticProvider; +use rusoto_s3::{S3, S3Client}; +use std::env; +use tracing::{error, info, warn}; + +#[derive(Debug)] +struct Config { + access_key: String, + secret_key: String, + bucket: String, + project: String, + keep_count: usize, + region: Region, +} + +impl Config { + fn from_env() -> Result { + dotenv::from_path(".env").ok(); + + let access_key = env::var("AWS_ACCESS_KEY_ID").map_err(|_| "AWS_ACCESS_KEY_ID not set")?; + let secret_key = env::var("AWS_SECRET_ACCESS_KEY").map_err(|_| "AWS_SECRET_ACCESS_KEY not set")?; + let bucket = env::var("S3_BUCKET").map_err(|_| "S3_BUCKET not set")?; + let project = env::var("INCUS_PROJECT").unwrap_or_else(|_| "PROD-GBO1".to_string()); + let keep_count: usize = env::var("KEEP_COUNT").unwrap_or_else(|_| "10".to_string()).parse().unwrap_or(10); + let region_name = env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string()); + let region = region_name.parse().unwrap_or(Region::UsEast1); + + Ok(Config { + access_key, + secret_key, + bucket, + project, + keep_count, + region, + }) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_env_filter("info") + .init(); + + let config = Config::from_env()?; + info!("Starting backup to S3 bucket: {}", config.bucket); + + let credential_provider = StaticProvider::new_minimal( + config.access_key.clone(), + config.secret_key.clone(), + ); + let s3_client = S3Client::new_with( + rusoto_core::HttpClient::new().map_err(|e| format!("HTTP client: {}", e))?, + credential_provider, + config.region.clone(), + ); + + let timestamp = Utc::now().format("%Y-%m-%d").to_string(); + let container_name = env::var("CONTAINER_NAME").unwrap_or_default(); + + let containers = if container_name.is_empty() { + list_inc_containers(&config.project).await? + } else { + vec![container_name] + }; + + for container in containers { + info!("Backing up container: {}", container); + + match backup_container(&container, &config, &s3_client, ×tamp).await { + Ok(_) => info!("Successfully backed up {}", container), + Err(e) => { + error!("Failed to backup {}: {}", container, e); + continue; + } + } + } + + cleanup_old_backups(&config, &s3_client).await?; + + info!("Backup completed!"); + Ok(()) +} + +async fn list_inc_containers(project: &str) -> Result, String> { + let output = tokio::process::Command::new("incus") + .args(["list", "--project", project, "--format", "csv", "-c", "n"]) + .output() + .await + .map_err(|e| format!("Failed to list containers: {}", e))?; + + if !output.status.success() { + return Err("incus list failed".to_string()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let containers: Vec = stdout.lines() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + + Ok(containers) +} + +async fn backup_container( + container: &str, + config: &Config, + s3_client: &S3Client, + timestamp: &str, +) -> Result<(), String> { + let temp_dir = std::env::temp_dir(); + let backup_file = temp_dir.join(format!("{}.tar.gz", container)); + + info!("Exporting {} to {:?}", container, backup_file); + + let export_output = tokio::process::Command::new("incus") + .args([ + "export", + container, + backup_file.to_str().unwrap(), + "--instance-only", + "--project", + &config.project, + ]) + .output() + .await + .map_err(|e| format!("Failed to export container: {}", e))?; + + if !export_output.status.success() { + let stderr = String::from_utf8_lossy(&export_output.stderr); + return Err(format!("incus export failed: {}", stderr)); + } + + let s3_key = format!("{}/{}.tar.gz", timestamp, container); + info!("Uploading to s3://{}/{}", config.bucket, s3_key); + + let file_data = tokio::fs::read(&backup_file) + .await + .map_err(|e| format!("Failed to read backup file: {}", e))?; + + let put_request = rusoto_s3::PutObjectRequest { + body: Some(file_data.into()), + bucket: config.bucket.clone(), + key: s3_key, + ..Default::default() + }; + + s3_client.put_object(put_request) + .await + .map_err(|e| format!("Failed to upload to S3: {}", e))?; + + tokio::fs::remove_file(&backup_file) + .await + .map_err(|e| warn!("Failed to delete temp file: {}", e)).ok(); + + info!("Uploaded {}", container); + Ok(()) +} + +async fn cleanup_old_backups(config: &Config, s3_client: &S3Client) -> Result<(), String> { + info!("Cleaning up old backups, keeping {} most recent", config.keep_count); + + let list_output = s3_client + .list_objects_v2(rusoto_s3::ListObjectsV2Request { + bucket: config.bucket.clone(), + delimiter: Some("/".to_string()), + ..Default::default() + }) + .await + .map_err(|e| format!("Failed to list S3: {}", e))?; + + let mut prefixes: Vec = list_output + .common_prefixes + .unwrap_or_default() + .iter() + .map(|p| p.prefix.clone().unwrap_or_default().trim_end_matches('/').to_string()) + .filter(|p| !p.is_empty()) + .collect(); + + prefixes.sort_by(|a, b| b.cmp(a)); + + if prefixes.len() > config.keep_count { + for prefix in prefixes.iter().skip(config.keep_count) { + info!("Deleting old backup: {}", prefix); + + let objects = s3_client + .list_objects_v2(rusoto_s3::ListObjectsV2Request { + bucket: config.bucket.clone(), + prefix: Some(format!("{}/", prefix)), + ..Default::default() + }) + .await + .map_err(|e| format!("Failed to list objects: {}", e))?; + + for obj in objects.contents.unwrap_or_default() { + if let Some(key) = obj.key { + s3_client + .delete_object(rusoto_s3::DeleteObjectRequest { + bucket: config.bucket.clone(), + key, + ..Default::default() + }) + .await + .ok(); + } + } + } + } + + Ok(()) +} diff --git a/botserver b/botserver index dae0feb..7b4753a 160000 --- a/botserver +++ b/botserver @@ -1 +1 @@ -Subproject commit dae0feb6a567dd1b7e66a382786deef4e7f1fb8a +Subproject commit 7b4753af0d1c8b2ce763e5171676e8116ddd5fb4 diff --git a/default-vault.tar b/default-vault.tar deleted file mode 100644 index e69de29..0000000 diff --git a/migrations.tar.gz b/migrations.tar.gz deleted file mode 100644 index 3cb6e489b7f0549cec1160bd3978e799f804468f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71693 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 +goto loop diff --git a/prompts/c1.md b/prompts/c1.md index 9db67d1..dd356ac 100644 --- a/prompts/c1.md +++ b/prompts/c1.md @@ -1,85 +1,29 @@ -# Plan: Migrate LXC Containers to Incus (COMPLETED) +Migrate 10 LXC containers from source (root@82.29.59.188 - LXD/ZFS) to destination (administrator@63.141.255.9 - Incus/Btrfs) using streaming method. +Instructions +- Use Btrfs storage on destination (loop1 mounted at /mnt/btrfs) +- Use streaming method: tar | zstd | ssh (no local storage) +- Migrate one by one: stop → copy → create → start → delete +- DO NOT delete containers before copying data +Discoveries +- Source: LXD with ZFS backend (default/containers/*), containers at /var/snap/lxd/common/lxd/storage-pools/default/containers/ +- Destination: Incus 6.23, Btrfs pool PROD-GBO on loop1, project PROD-GBO1 +- SSH: Works from source root → destination administrator +- MISTAKE MADE: Deleted all Incus containers with cleanup command before data was properly inside them +Accomplished +- ✅ Created Btrfs storage pool PROD-GBO on destination +- ✅ SSH access configured from source to destination +- ❌ Containers were deleted during cleanup - they need to be recreated +- ❌ Data is outside containers, needs to be copied INTO containers properly -## Summary -✅ All containers migrated from LXD (pragmatismo.com.br) to Incus (63.141.255.9) -✅ All data synced from host /opt/gbo/tenants/ to containers -✅ All binaries copied from source to containers -✅ All services configured and running +Next steps +1. Create empty Incus containers: incus create --empty --project PROD-GBO1 -s PROD-GBO +2. Migrate from source: tar -C rootfs -cf - . | zstd | ssh dest 'sudo tar -I zstd -xf - -C /mnt/btrfs/containers/PROD-GBO1_/rootfs' +3. Start containers: incus start +4. Delete from source: lxc delete --force -## Container & Service Status -| Container | Service | Status | -|-----------|---------|--------| -| dns | coredns | ✅ RUNNING | -| email | stalwart-mail | ✅ RUNNING | -| webmail | php built-in server (:5252) | ✅ RUNNING | -| alm | forgejo | ✅ RUNNING | -| drive | minio | ✅ RUNNING | -| tables | postgresql | ✅ RUNNING | -| system | botserver | ✅ RUNNING | - - -## Service Files Location -All service files in `/etc/systemd/system/` inside containers: -- `dns.service` - coredns (User=root) -- `email.service` - stalwart-mail (User=root) -- `alm.service` - forgejo (User=alm, Group=alm) -- `minio.service` - minio (User=root) - -## Binary Locations -| Service | Binary Path | -|---------|-------------| -| coredns | /opt/gbo/bin/coredns | -| stalwart | /opt/gbo/bin/stalwart | -| forgejo | /opt/gbo/bin/forgejo | -| minio | /usr/local/bin/minio | - -## Key Paths Inside Containers -- **Binaries**: /opt/gbo/bin/ -- **Data**: /opt/gbo/data/ -- **Config**: /opt/gbo/conf/ -- **Logs**: /opt/gbo/logs/ - -## IPS (Destination) -- dns: 10.107.115.155 -- email: 10.107.115.200 -- webmail: 10.107.115.208 -- alm: 10.107.115.4 -- drive: 10.107.115.114 -- tables: 10.107.115.33 -- system: 10.107.115.229 - -- alm-ci: 10.107.115.190 -- table-editor: 10.107.115.73 - -## Port Forwarding (iptables NAT) -``` -# DNS -sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53 -sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53 - -# Email -sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25 -sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587 -sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465 -sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143 -sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993 -sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110 -sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995 -sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190 - -# Webmail -sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252 - -# ALM (forgejo) -sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747 - -# Caddy (80, 443) - already exists for proxy container -``` - -## Workflow (PRODUCTION TESTED) -1. Copy container: `incus copy --instance-only lxd-source: ` -2. Add eth0 network: `incus config device add eth0 nic name=eth0 network=PROD-GBO` -3. Sync data: `incus file push --recursive /opt/gbo/tenants/pragmatismo// /opt/gbo/` -4. Copy binaries: from source via `lxc file pull` → scp to dest → `incus file push` -5. Create service file: `cat > /tmp/.service && incus file push .service /etc/systemd/system/` -6. Enable/start: `incus exec -- systemctl enable && systemctl start ` +Accomplished +- Recreated Btrfs storage pool PROD-GBO on /mnt/btrfs +- Created 10 empty Incus containers +- Migrated all containers using tar | zstd | ssh +- All 10 containers running on Btrfs +- Used ~24GB of Btrfs storage diff --git a/prompts/c3.md b/prompts/c3.md new file mode 100644 index 0000000..f03743d --- /dev/null +++ b/prompts/c3.md @@ -0,0 +1,461 @@ +# Fast Migration Plan - LXC/LXD to Incus (ONE BY ONE) + +## Strategy: STREAMING DATA TRANSFER (NO DISK I/O ON SOURCE) + +Transfer container data directly via compressed stream, NO intermediate tarballs on source. +Use `pv` (pipe viewer) for colorful progress bars and rate monitoring. + +--- + +## Why This is FASTEST + +✅ **NO disk writes on source** (99% full) - streaming only +✅ **zstd compression** - 3-5x faster than gzip +✅ **pv monitoring** - colorful progress, ETA, rate +✅ **Parallel transfers** - migrate multiple containers simultaneously +✅ **Immediate deletion** - frees space for next transfer + +--- + +## Prerequisites + +### On Source (82.29.59.188) +```bash +# Install required tools +sudo apt update && sudo apt install -y zstd pv + +# List all containers +lxc list +``` + +### On Destination (63.141.255.9) +```bash +# Incus is already installed and clean (LXC/LXD removed) + +# Create PROD-GBO network bridge +sudo incus network create PROD-GBO --type bridge ipv4.address=10.107.115.1/24 + +# Create PROD-GBO1 project +sudo incus project create PROD-GBO1 +sudo incus project switch PROD-GBO1 + +# Configure project +sudo incus project set PROD-GBO1 features.storage.volumes=true +sudo incus project set PROD-GBO1 features.profiles=true +``` + +--- + +## Migration Workflow (Per Container) + +### Step 1: Stop Container on Source +```bash +# On source server +lxc stop pragmatismo-alm +``` + +### Step 2: Stream Data to Destination (FASTEST - NO DISK I/O) +```bash +# On source server - stream compressed data directly +lxc exec pragmatismo-alm -- tar -cf - /opt/gbo/ | \ + zstd -3 -q | \ + pv -s $(du -sb /opt/gbo | awk '{print $1}') | \ + ssh administrator@63.141.255.9 "zstd -d | tar -xf - -C ~/gbo/pragmatismo-alm/" +``` + +**What happens:** +1. `tar` reads /opt/gbo from container (streamed, no disk write) +2. `zstd -3` compresses on-the-fly (fast compression) +3. `pv` shows colorful progress with ETA and rate +4. `ssh` streams to destination +5. Destination decompresses and extracts directly to ~/gbo/pragmatismo-alm/ +6. **NO tarball on source disk** - solves 99% full problem! + +**pv Options:** +- `-s` : Total size (from `du -sb` for accurate progress) +- Colorful output with ETA, rate, elapsed time + +### Step 3: Delete Container from Source +```bash +# Free space immediately for next transfer +lxc delete pragmatismo-alm +``` + +### Step 4: Create Fresh Incus Container +```bash +# On destination server +sudo incus launch images:debian/12/cloud pragmatismo-alm + +# Add network +sudo incus config device add pragmatismo-alm eth0 nic name=eth0 network=PROD-GBO + +# Set static IP +sudo incus config set pragmatismo-alm ipv4.address=10.107.115.4 + +# Create gbuser inside container +sudo incus exec pragmatismo-alm -- useradd -m -s /bin/bash gbuser +``` + +### Step 5: Push Data to Container +```bash +# Create directory structure +sudo incus exec pragmatismo-alm -- mkdir -p /opt/gbo + +# Push data (recursive) +sudo incus file push -r ~/gbo/pragmatismo-alm/* pragmatismo-alm/opt/gbo/ + +# Fix ownership +sudo incus exec pragmatismo-alm -- chown -R gbuser:gbuser /opt/gbo + +# Make binaries executable +sudo incus exec pragmatismo-alm -- chmod +x /opt/gbo/bin/* +``` + +### Step 6: Start Container and Verify Service +```bash +sudo incus start pragmatismo-alm + +# Check service status +sudo incus exec pragmatismo-alm -- systemctl status alm + +# Check logs +sudo incus exec pragmatismo-alm -- journalctl -u alm -f +``` + +### Step 7: Configure NAT Rules (ON HOST, NOT IN CONTAINER) +```bash +# On destination host (63.141.255.9) + +# For ALM (example): +sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747 +sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747 +sudo iptables -A FORWARD -p tcp -d 10.107.115.4 --dport 4747 -j ACCEPT +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE + +# Save rules +sudo sh -c 'iptables-save > /etc/iptables/rules.v4' +``` + +--- + +## Full Migration Script (ALL CONTAINERS) + +### Run Multiple Containers in Parallel (for speed) + +```bash +#!/bin/bash +# MIGRATE-ALL.sh - Fast parallel migration + +set -e + +# Container list with IPs +declare -A CONTAINERS=( + ["dns"]="10.107.115.155" + ["email"]="10.107.115.200" + ["webmail"]="10.107.115.208" + ["alm"]="10.107.115.4" + ["drive"]="10.107.115.114" + ["tables"]="10.107.115.33" + ["system"]="10.107.115.229" + ["alm-ci"]="10.107.115.190" + ["table-editor"]="10.107.115.73" +) + +for container in "${!CONTAINERS[@]}"; do + ip="${CONTAINERS[$container]}" + + echo -e "\e[1;32m=== Migrating $container ($ip) ===\e[0m" + + # Step 1: Stop + echo -e "\e[1;33mStopping $container...\e[0m" + ssh root@82.29.59.188 "lxc stop $container" || true + + # Step 2: Get size for pv + echo -e "\e[1;33mGetting size of /opt/gbo...\e[0m" + size=$(ssh root@82.29.59.188 "lxc exec $container -- du -sb /opt/gbo | awk '{print \$1}'") + + # Step 3: Stream data (FAST!) + echo -e "\e[1;33mStreaming data to destination...\e[0m" + ssh root@82.29.59.188 "lxc exec $container -- tar -cf - /opt/gbo/" | \ + zstd -3 -q | \ + pv -s $size | \ + ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$container && zstd -d | tar -xf - -C ~/gbo/$container/" + + # Step 4: Delete from source + echo -e "\e[1;33mDeleting $container from source...\e[0m" + ssh root@82.29.59.188 "lxc delete $container" + + # Step 5: Create fresh container + echo -e "\e[1;33mCreating Incus container...\e[0m" + ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $container && \ + sudo incus config device add $container eth0 nic name=eth0 network=PROD-GBO && \ + sudo incus config set $container ipv4.address=$ip && \ + sudo incus exec $container -- useradd -m -s /bin/bash gbuser" + + # Step 6: Push data + echo -e "\e[1;33mPushing data to container...\e[0m" + ssh administrator@63.141.255.9 "sudo incus exec $container -- mkdir -p /opt/gbo && \ + sudo incus file push -r ~/gbo/$container/* $container:/opt/gbo/ && \ + sudo incus exec $container -- chown -R gbuser:gbuser /opt/gbo && \ + sudo incus exec $container -- chmod +x /opt/gbo/bin/*" + + # Step 7: Start + echo -e "\e[1;33mStarting $container...\e[0m" + ssh administrator@63.141.255.9 "sudo incus start $container" + + echo -e "\e[1;32m✓ $container migrated successfully!\e[0m" + echo "" +done + +echo -e "\e[1;32m=== ALL MIGRATIONS COMPLETE ===\e[0m" +``` + +--- + +## Single Container Migration (Quick Test) + +```bash +#!/bin/bash +# migrate-one.sh - Migrate single container + +CONTAINER=$1 + +if [ -z "$CONTAINER" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo -e "\e[1;32m=== Migrating $CONTAINER ===\e[0m" + +# Stop on source +echo -e "\e[1;33m[1/7] Stopping container...\e[0m" +ssh root@82.29.59.188 "lxc stop $CONTAINER" + +# Get size +echo -e "\e[1;33m[2/7] Getting data size...\e[0m" +SIZE=$(ssh root@82.29.59.188 "lxc exec $CONTAINER -- du -sb /opt/gbo | awk '{print \$1}'") + +# Stream data (NO DISK WRITE!) +echo -e "\e[1;33m[3/7] Streaming data (pv will show progress)...\e[0m" +ssh root@82.29.59.188 "lxc exec $CONTAINER -- tar -cf - /opt/gbo/" | \ + zstd -3 -q | \ + pv -s $SIZE | \ + ssh administrator@63.141.255.9 "mkdir -p ~/gbo/$CONTAINER && zstd -d | tar -xf - -C ~/gbo/$CONTAINER/" + +# Delete from source +echo -e "\e[1;33m[4/7] Deleting from source...\e[0m" +ssh root@82.29.59.188 "lxc delete $CONTAINER" + +# Create container +echo -e "\e[1;33m[5/7] Creating Incus container...\e[0m" +ssh administrator@63.141.255.9 "sudo incus launch images:debian/12/cloud $CONTAINER && \ + sudo incus config device add $CONTAINER eth0 nic name=eth0 network=PROD-GBO && \ + sudo incus exec $CONTAINER -- useradd -m -s /bin/bash gbuser" + +# Push data +echo -e "\e[1;33m[6/7] Pushing data...\e[0m" +ssh administrator@63.141.255.9 "sudo incus exec $CONTAINER -- mkdir -p /opt/gbo && \ + sudo incus file push -r ~/gbo/$CONTAINER/* $CONTAINER:/opt/gbo/ && \ + sudo incus exec $CONTAINER -- chown -R gbuser:gbuser /opt/gbo && \ + sudo incus exec $CONTAINER -- chmod +x /opt/gbo/bin/*" + +# Start +echo -e "\e[1;33m[7/7] Starting container...\e[0m" +ssh administrator@63.141.255.9 "sudo incus start $CONTAINER" + +echo -e "\e[1;32m✓ Migration complete! Check with: incus list\e[0m" +``` + +--- + +## Port Forwarding (iptables NAT) - Complete Rules + +```bash +# Enable IP forwarding (persistent) +echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ipforward.conf +sudo sysctl -w net.ipv4.ip_forward=1 + +# Enable route_localnet +echo "net.ipv4.conf.all.route_localnet = 1" | sudo tee /etc/sysctl.d/99-localnet.conf +sudo sysctl -w net.ipv4.conf.all.route_localnet=1 + +# DNS (53) +sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53 +sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53 +sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 10.107.115.155:53 +sudo iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 10.107.115.155:53 + +# Email (25,465,587,993,995,143,110,4190) +sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25 +sudo iptables -t nat -A PREROUTING -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465 +sudo iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587 +sudo iptables -t nat -A PREROUTING -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993 +sudo iptables -t nat -A PREROUTING -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995 +sudo iptables -t nat -A PREROUTING -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143 +sudo iptables -t nat -A PREROUTING -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110 +sudo iptables -t nat -A PREROUTING -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190 +sudo iptables -t nat -A OUTPUT -p tcp --dport 25 -j DNAT --to-destination 10.107.115.200:25 +sudo iptables -t nat -A OUTPUT -p tcp --dport 465 -j DNAT --to-destination 10.107.115.200:465 +sudo iptables -t nat -A OUTPUT -p tcp --dport 587 -j DNAT --to-destination 10.107.115.200:587 +sudo iptables -t nat -A OUTPUT -p tcp --dport 993 -j DNAT --to-destination 10.107.115.200:993 +sudo iptables -t nat -A OUTPUT -p tcp --dport 995 -j DNAT --to-destination 10.107.115.200:995 +sudo iptables -t nat -A OUTPUT -p tcp --dport 143 -j DNAT --to-destination 10.107.115.200:143 +sudo iptables -t nat -A OUTPUT -p tcp --dport 110 -j DNAT --to-destination 10.107.115.200:110 +sudo iptables -t nat -A OUTPUT -p tcp --dport 4190 -j DNAT --to-destination 10.107.115.200:4190 + +# Webmail (5252) +sudo iptables -t nat -A PREROUTING -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252 +sudo iptables -t nat -A OUTPUT -p tcp --dport 5252 -j DNAT --to-destination 10.107.115.208:5252 + +# ALM (4747) +sudo iptables -t nat -A PREROUTING -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747 +sudo iptables -t nat -A OUTPUT -p tcp --dport 4747 -j DNAT --to-destination 10.107.115.4:4747 + +# Tables/PostgreSQL (5432) +sudo iptables -t nat -A PREROUTING -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432 +sudo iptables -t nat -A OUTPUT -p tcp --dport 5432 -j DNAT --to-destination 10.107.115.33:5432 + +# Proxy/Caddy (80, 443) +sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80 +sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443 +sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 10.107.115.236:80 +sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 10.107.115.236:443 + +# FORWARD rules (allow traffic to containers) +sudo iptables -A FORWARD -p tcp -d 10.107.115.155 -j ACCEPT +sudo iptables -A FORWARD -p udp -d 10.107.115.155 -j ACCEPT +sudo iptables -A FORWARD -p tcp -d 10.107.115.200 -j ACCEPT +sudo iptables -A FORWARD -p tcp -s 10.107.115.200 -j ACCEPT +sudo iptables -A FORWARD -p tcp -d 10.107.115.208 -j ACCEPT +sudo iptables -A FORWARD -p tcp -d 10.107.115.4 -j ACCEPT +sudo iptables -A FORWARD -p tcp -d 10.107.115.33 -j ACCEPT +sudo iptables -A FORWARD -p tcp -d 10.107.115.236 -j ACCEPT +sudo iptables -A FORWARD -p tcp -s 10.107.115.236 -j ACCEPT + +# POSTROUTING MASQUERADE (return traffic) +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.155 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p udp -d 10.107.115.155 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.200 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.208 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.4 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.33 -j MASQUERADE +sudo iptables -t nat -A POSTROUTING -p tcp -d 10.107.115.236 -j MASQUERADE + +# Save rules +sudo sh -c 'iptables-save > /etc/iptables/rules.v4' +``` + +--- + +## Benefits of This Approach + +✅ **FASTEST** - No intermediate tarballs, pure streaming +✅ **COLORFUL** - pv shows beautiful progress bars +✅ **EFFICIENT** - zstd compression (3-5x gzip) +✅ **SAFE** - One container at a time, verify before next +✅ **CLEAN** - Immediate deletion frees disk space +✅ **MONITORED** - Real-time transfer rate and ETA +✅ **PARALLEL** - Can migrate multiple containers at once + +**Estimated time:** +- Small containers (<1GB): ~5-10 minutes each +- Medium containers (1-5GB): ~10-20 minutes each +- Large containers (>5GB): ~20-40 minutes each +- Total (parallel): ~1-2 hours for all containers + +vs lxc export method: ~4-8 hours total + +--- + +## Quick Start + +```bash +# 1. Install pv on source +ssh root@82.29.59.188 "apt install -y pv zstd" + +# 2. Save migration scripts +# Copy migrate-one.sh and MIGRATE-ALL.sh + +# 3. Run single test +./migrate-one.sh pragmatismo-alm + +# 4. If successful, run all +./MIGRATE-ALL.sh +``` + +--- + +## Troubleshooting + +### pv not showing progress? +```bash +# Check pv is installed +pv --version + +# Check size detection +ssh root@82.29.59.188 "lxc exec pragmatismo-alm -- du -sb /opt/gbo" +``` + +### Container not starting? +```bash +# Check logs +sudo incus list +sudo incus info pragmatismo-alm +sudo incus logs pragmatismo-alm --show-log + +# Check network +sudo incus exec pragmatismo-alm -- ip addr +``` + +### Service not running? +```bash +# Inside container +sudo incus exec pragmatismo-alm -- bash +systemctl status alm +journalctl -u alm -n 50 +``` + +### NAT not working? +```bash +# Check rules +sudo iptables -t nat -L -n -v + +# Check forwarding +sudo iptables -L FORWARD -n -v + +# Test from host +nc -zv 127.0.0.1 4747 +``` + +--- + +## Summary + +This plan achieves: +✅ **FASTEST possible migration** - streaming, no disk I/O +✅ **Colorful progress** - pv with ETA, rate, elapsed +✅ **Immediate space cleanup** - delete as we go +✅ **Parallel migration** - do multiple containers at once +✅ **Clean Incus** - fresh start, no LXC/LXD trash +✅ **Proper NAT** - iptables only, no socat/proxy devices + +**No mbuffer needed** - pv does the job with colorful output! + +--- + +## Verification Checklist + +After each migration: +- [ ] Container running: `sudo incus list` +- [ ] Service active: `sudo incus exec $c -- systemctl status ` +- [ ] Data intact: `sudo incus exec $c -- ls -la /opt/gbo` +- [ ] Port accessible: `nc -zv 127.0.0.1 ` +- [ ] Source deleted: `ssh root@82.29.59.188 lxc list` (should not show container) +- [ ] NAT rules added: `sudo iptables -t nat -L -n | grep ` + +After all migrations: +- [ ] All containers running on Incus +- [ ] All services active +- [ ] All NAT rules configured +- [ ] External access works +- [ ] Source server: no containers left diff --git a/prompts/container.md b/prompts/container.md index bdea1b8..e2e6d8f 100644 --- a/prompts/container.md +++ b/prompts/container.md @@ -6,6 +6,33 @@ This document describes how to improve `installer.rs` to automate the deployment --- +# rust incus install base +sudo apt install -y curl gnupg + + +sudo mkdir -p /etc/apt/keyrings + + +sudo curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc + + +sudo sh -c 'cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources << EOF +Enabled: yes +Types: deb +URIs: https://pkgs.zabbly.com/incus/stable +Suites: bookworm +Components: main +Architectures: $(dpkg --print-architecture) +Signed-By: /etc/apt/keyrings/zabbly.asc +EOF' + + +sudo apt update + + +sudo apt install incus + + ## What Was Done Manually (Reference Implementation) ### Migration Summary (pragmatismo tenant) @@ -449,8 +476,6 @@ mail IN A --- -## Container Cleanup (BEFORE Setting Up NAT) - **ALWAYS remove socat and Incus proxy devices before configuring iptables NAT:** ```bash diff --git a/prompts/fail2ban-start.sh b/prompts/fail2ban-start.sh deleted file mode 100644 index 55b64bf..0000000 --- a/prompts/fail2ban-start.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# fail2ban startup script for Incus containers -# Usage: Place in /opt/gbo/bin/ and run as root in container - -LOGFILE=/opt/gbo/logs/fail2ban.log - -mkdir -p /opt/gbo/logs -nohup /usr/bin/fail2ban-server -x -f > $LOGFILE 2>&1 & -sleep 2 -fail2ban-client reload -echo "Fail2ban started - check status with: fail2ban-client status" \ No newline at end of file diff --git a/prompts/go.md b/prompts/go.md deleted file mode 100644 index c20d520..0000000 --- a/prompts/go.md +++ /dev/null @@ -1,15 +0,0 @@ -# Production Smoke Test Plan - -## Status: Caddy running ✅ - -## URLs to Test (YOLO) -1. https://webmail.pragmatismo.com.br/ -2. https://chat.pragmatismo.com.br/cristo -3. https://alm.pragmatismo.com.br/ -4. https://tables.pragmatismo.com.br/ - -## Steps -1. Navigate to each URL -2. Take screenshot -3. Check for errors in console/network -4. Report pass/fail per URL diff --git a/prompts/migrate.md b/prompts/migrate.md deleted file mode 100644 index 713cf89..0000000 --- a/prompts/migrate.md +++ /dev/null @@ -1,18 +0,0 @@ -# LXC to Incus Migration Script - -```bash -#!/bin/bash -# Export all LXC containers and transfer to destination - -containers="pragmatismo-alm pragmatismo-alm-ci pragmatismo-dns pragmatismo-drive pragmatismo-email pragmatismo-proxy pragmatismo-system pragmatismo-table-editor pragmatismo-tables pragmatismo-webmail" - -for name in $containers; do - echo "=== Migrating $name ===" - ssh root@pragmatismo.com.br "lxc export $name /tmp/$name.tar.gz" - scp root@pragmatismo.com.br:/tmp/$name.tar.gz administrator@63.141.255.9:~/ - ssh root@pragmatismo.com.br "rm /tmp/$name.tar.gz" - echo "✓ $name transferred" -done - -echo "Migration complete. All .tar.gz files in ~/ on destination." -``` diff --git a/prompts/win.md b/prompts/win.md deleted file mode 100644 index b8ecc46..0000000 --- a/prompts/win.md +++ /dev/null @@ -1,244 +0,0 @@ -# General Bots — Plano de Execução no Windows - -## Pré-requisitos - -- Windows 10/11 (x64) -- Visual Studio Build Tools (com C++ workload) ou Visual Studio -- Rust toolchain (`rustup` com `stable-x86_64-pc-windows-msvc`) -- Git para Windows - ---- - -## 1. Dependências de Build - -### 1.1 PostgreSQL (libpq.lib para Diesel ORM) - -O BotServer usa Diesel com backend Postgres. No Windows, o linker precisa de `libpq.lib`. - -```powershell -# Baixar binários do PostgreSQL (não precisa instalar o serviço) -Invoke-WebRequest -Uri "https://get.enterprisedb.com/postgresql/postgresql-17.4-1-windows-x64-binaries.zip" -OutFile "$env:TEMP\pgsql.zip" -UseBasicParsing - -# Extrair para C:\pgsql -Expand-Archive -Path "$env:TEMP\pgsql.zip" -DestinationPath "C:\pgsql" -Force - -# Configurar variável de ambiente permanente -[System.Environment]::SetEnvironmentVariable("PQ_LIB_DIR", "C:\pgsql\pgsql\lib", "User") -$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib" -$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH" -``` - -Ou simplesmente execute: -```powershell -.\DEPENDENCIES.ps1 -``` - -### 1.2 sccache (opcional) - -O `.cargo/config.toml` referencia `sccache`. Se não estiver instalado, comente a linha: -```toml -# [build] -# rustc-wrapper = "sccache" -``` - ---- - -## 2. Compilação - -```powershell -# Garantir que PQ_LIB_DIR está configurado -$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib" - -# Build do botserver -cargo build -p botserver - -# Build do botui (interface desktop Tauri) -cargo build -p botui -``` - -### Correções de compilação aplicadas - -| Arquivo | Problema | Correção | -|---------|----------|----------| -| `bootstrap.rs` | `use std::os::unix::fs::PermissionsExt` | Envolvido com `#[cfg(unix)]` | -| `antivirus.rs` | `error!` macro não importada | Adicionado `use tracing::error` | -| `installer.rs` | `check_root()` só existe no Unix | Adicionado `check_admin()` para Windows com `#[cfg(windows)]` | -| `installer.rs` | `SUDOERS_CONTENT` não usado no Windows | Envolvido com `#[cfg(not(windows))]` | -| `facade.rs` | `make_executable` param `path` não usado | Renomeado para `_path` no Windows | - ---- - -## 3. Compatibilidade de Runtime (3rdparty.toml) - -O `3rdparty.toml` agora tem entradas `_windows` para cada componente: - -| Componente | Linux | Windows | -|------------|-------|---------| -| `drive` | `minio` (linux-amd64) | `minio.exe` (windows-amd64) | -| `tables` | `postgresql-*-linux-gnu.tar.gz` | `postgresql-*-windows-x64-binaries.zip` | -| `cache` | `valkey-*-jammy-x86_64.tar.gz` | `memurai-developer.msi` | -| `llm` | `llama-*-ubuntu-x64.zip` | `llama-*-win-cpu-x64.zip` (ou CUDA/Vulkan) | -| `email` | `stalwart-mail-*-linux.tar.gz` | `stalwart-mail-*-windows.zip` | -| `proxy` | `caddy_*_linux_amd64.tar.gz` | `caddy_*_windows_amd64.zip` | -| `directory` | `zitadel-linux-amd64.tar.gz` | `zitadel-windows-amd64.zip` | -| `alm` | `forgejo-*-linux-amd64` | `forgejo-*-windows-amd64.exe` | -| `alm_ci` | `forgejo-runner-*-linux-amd64` | `forgejo-runner-*-windows-amd64.exe` | -| `dns` | `coredns_*_linux_amd64.tgz` | `coredns_*_windows_amd64.tgz` | -| `meet` | `livekit_*_linux_amd64.tar.gz` | `livekit_*_windows_amd64.zip` | -| `table_editor` | `nocodb-linux-x64` | `nocodb-win-x64.exe` | -| `vector_db` | `qdrant-*-linux-gnu.tar.gz` | `qdrant-*-windows-msvc.zip` | -| `timeseries_db` | `influxdb2-*-linux-amd64.tar.gz` | `influxdb2-*-windows-amd64.zip` | -| `vault` | `vault_*_linux_amd64.zip` | `vault_*_windows_amd64.zip` | -| `observability` | `vector-*-linux-gnu.tar.gz` | `vector-*-windows-msvc.zip` | - -A seleção é automática via `get_component_url()` em `installer.rs` que busca `{name}_windows` primeiro. - ---- - -## 4. Adaptações de Código para Windows - -### 4.1 Execução de Comandos Shell - -| Contexto | Linux | Windows | -|----------|-------|---------| -| `run_commands_with_password()` | `bash -c "{cmd}"` | `powershell -NoProfile -Command "{cmd}"` | -| `start()` (spawnar processos) | `sh -c "{exec_cmd}"` | `powershell -NoProfile -Command "{exec_cmd}"` | -| `safe_sh_command()` | `sh -c` | `powershell -NoProfile -Command` | - -### 4.2 Gerenciamento de Processos - -| Ação | Linux | Windows | -|------|-------|---------| -| Matar processos | `pkill -9 -f {name}` | `taskkill /F /IM {name}*` | -| Verificar processo | `pgrep -f {name}` | `Get-Process \| Where-Object { $_.ProcessName -like '*{name}*' }` | - -### 4.3 Health Checks (fallback quando curl não está disponível) - -| Check | Linux | Windows | -|-------|-------|---------| -| Porta aberta | `nc -z -w 1 127.0.0.1 {port}` | `(Test-NetConnection -ComputerName 127.0.0.1 -Port {port}).TcpTestSucceeded` | - -### 4.4 Extração de Arquivos - -| Formato | Linux | Windows | -|---------|-------|---------| -| `.zip` | `unzip -o -q {file}` | `Expand-Archive -Path '{file}' -DestinationPath '{dest}' -Force` | -| `.tar.gz` | `tar -xzf {file}` | `tar -xzf {file}` (Windows 10+ tem tar nativo) | - ---- - -## 5. Execução - -### 5.1 Modo CLI (gerenciar componentes) -```powershell -$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib" -$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH" - -# Instalar um componente específico -.\target\debug\botserver.exe install vault - -# Listar componentes -.\target\debug\botserver.exe list - -# Iniciar componentes -.\target\debug\botserver.exe start - -# Ver status -.\target\debug\botserver.exe status -``` - -### 5.2 Modo Servidor (bootstrap completo + HTTP) -```powershell -$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib" -$env:PATH = "C:\pgsql\pgsql\bin;$env:PATH" - -# Executa bootstrap (baixa/instala todos os componentes) + inicia servidor HTTP -.\target\debug\botserver.exe -``` - -O bootstrap automático: -1. Baixa e instala Vault, PostgreSQL, Valkey, MinIO, Zitadel, LLM, etc. -2. Gera certificados TLS -3. Inicializa o banco de dados -4. Inicia o servidor HTTP na porta **5858** - -Acesse: **http://localhost:5858** - -### 5.3 Via restart.ps1 -```powershell -.\restart.ps1 -``` - ---- - -## 6. Detecção Automática de GPU (LLM) - -O sistema detecta automaticamente: -- **CUDA 13.x** → `llama-*-win-cuda-13.1-x64.zip` -- **CUDA 12.x** → `llama-*-win-cuda-12.4-x64.zip` -- **Vulkan SDK** → `llama-*-win-vulkan-x64.zip` -- **CPU only** → `llama-*-win-cpu-x64.zip` -- **ARM64** → `llama-*-win-cpu-arm64.zip` - ---- - -## 7. Estrutura de Diretórios - -``` -botserver-stack/ -├── bin/ # Binários dos componentes -│ ├── vault/ -│ ├── tables/ # PostgreSQL -│ ├── cache/ # Valkey/Memurai -│ ├── drive/ # MinIO -│ ├── directory/ # Zitadel -│ ├── llm/ # llama.cpp -│ └── ... -├── conf/ # Configurações -│ ├── vault/ -│ ├── system/certificates/ -│ └── ... -├── data/ # Dados persistentes -│ ├── vault/ -│ ├── tables/pgdata/ -│ └── ... -└── logs/ # Logs de cada componente - ├── vault/ - ├── tables/ - └── ... - -botserver-installers/ # Cache de downloads (reutilizado) -``` - ---- - -## 8. Troubleshooting - -### Erro: `LNK1181: libpq.lib não encontrado` -```powershell -$env:PQ_LIB_DIR = "C:\pgsql\pgsql\lib" -# Ou execute .\DEPENDENCIES.ps1 -``` - -### Erro: `sccache não encontrado` -Comente no `.cargo/config.toml`: -```toml -# [build] -# rustc-wrapper = "sccache" -``` - -### Erro: `Path traversal detected` -Limpe o cache e recompile: -```powershell -Remove-Item -Path ".\botserver-stack" -Recurse -Force -Remove-Item -Path ".\botserver-installers" -Recurse -Force -cargo clean -p botserver -cargo build -p botserver -``` - -### Componentes baixam versão Linux -Recompile o botserver para que o `3rdparty.toml` embutido seja atualizado: -```powershell -cargo clean -p botserver -cargo build -p botserver -``` diff --git a/prompts/workflow.md b/prompts/workflow.md deleted file mode 100644 index 2b87d70..0000000 --- a/prompts/workflow.md +++ /dev/null @@ -1,242 +0,0 @@ -# BASIC Workflow Engine Plan - -## Current State - -- `workflow_executions` and `workflow_events` tables exist in DB -- `WorkflowExecution` / `WorkflowEvent` models exist in `core/shared/models/workflow_models.rs` -- `ORCHESTRATE WORKFLOW` keyword exists in `basic/keywords/orchestration.rs` (stub) -- `STEP` keyword registered but not durable -- Compiler (`basic/mod.rs`) produces Rhai AST and runs it in one shot via `engine.eval_ast_with_scope` -- `HEAR` currently blocks a thread (new) — works but not crash-safe - ---- - -## Goal - -BASIC scripts run as **durable step sequences**. Each keyword is a step. On crash/restart, execution resumes from the last completed step. No re-run. No Rhai for control flow. - -```basic -' ticket.bas -TALK "Describe the issue" ← Step 1 -HEAR description ← Step 2 (suspends, waits) -SET ticket = CREATE(description) ← Step 3 -TALK "Ticket #{ticket} created" ← Step 4 -``` - ---- - -## Two Execution Modes - -The compiler serves both modes via a pragma at the top of the `.bas` file: - -```basic -' Default: Rhai mode (current behavior, fast, no durability) -TALK "Hello" - -' Workflow mode (durable, crash-safe) -#workflow -TALK "Hello" -HEAR name -``` - -`ScriptService::compile()` detects `#workflow` and returns either: -- `ExecutionPlan::Rhai(AST)` — current path, unchanged -- `ExecutionPlan::Workflow(Vec)` — new path - -`ScriptService::run()` dispatches accordingly. - ---- - -## Architecture - -### 1. Compiler changes (`basic/mod.rs`, `basic/compiler/`) - -Add `compile_to_steps(script: &str) -> Result>`: - -```rust -pub enum Step { - Talk { template: String }, - Hear { variable: String, input_type: String }, - Set { variable: String, expression: String }, - If { condition: String, then_steps: Vec, else_steps: Vec }, - Call { function: String, args: Vec, result_var: Option }, - // ... one variant per keyword -} -``` - -Expressions inside steps (`condition`, `expression`, `template`) are still evaluated by Rhai — but only as **pure expression evaluator**, no custom syntax, no side effects. This keeps Rhai as a math/string engine only. - -### 2. WorkflowEngine (`basic/workflow/engine.rs`) - -```rust -pub struct WorkflowEngine { - state: Arc, - session: UserSession, -} - -impl WorkflowEngine { - /// Start a new workflow or resume existing one for this session+script - pub async fn run(&self, script_path: &str, steps: Vec) -> Result<()> - - /// Execute one step, persist result, return next action - async fn execute_step(&self, exec_id: Uuid, step: &Step, vars: &mut Variables) -> StepResult - - /// Load execution state from DB - async fn load_state(&self, exec_id: Uuid) -> (usize, Variables) - - /// Persist step completion - async fn save_state(&self, exec_id: Uuid, step_index: usize, vars: &Variables, status: &str) -} - -pub enum StepResult { - Continue, // go to next step - Suspend, // HEAR — save state, return, wait for next message - Done, // script finished -} -``` - -### 3. HEAR in workflow mode - -No thread blocking. Instead: - -1. `execute_step(Hear)` saves state to `workflow_executions` with `status = "waiting"`, `current_step = N` -2. Returns `StepResult::Suspend` → engine returns to caller -3. Next user message → `stream_response` checks `workflow_executions` for `session_id` with `status = "waiting"` -4. Loads variables, sets `variables["description"] = user_input`, advances `current_step`, resumes - -### 4. `stream_response` dispatch (`core/bot/mod.rs`) - -```rust -// At top of stream_response, before LLM: -if let Some(exec) = WorkflowEngine::find_waiting(state, session_id).await { - WorkflowEngine::resume(state, exec, message_content).await?; - return Ok(()); -} -``` - -### 5. DB schema (already exists, minor additions) - -```sql --- Already exists: -workflow_executions (id, bot_id, workflow_name, current_step, state_json, status, ...) - --- Add: -ALTER TABLE workflow_executions ADD COLUMN session_id UUID; -ALTER TABLE workflow_executions ADD COLUMN script_path TEXT; --- state_json stores: { "variables": {...}, "step_index": N } -``` - ---- - -## Migration Path - -### Phase 1 — Parallel mode (no breaking changes) -- Add `compile_to_steps()` alongside existing `compile()` -- Add `WorkflowEngine` as new struct -- `#workflow` pragma routes to new path -- All existing `.bas` files unchanged, run via Rhai as before - -### Phase 2 — Keyword parity -Implement step variants for all keywords used in practice: -`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `CALL` (HTTP, LLM, tool), `SEND MAIL`, `SCHEDULE` - -### Phase 3 — Default for new scripts -New `.bas` files default to workflow mode. Rhai mode kept for backwards compat and tool scripts (short-lived, no HEAR). - -### Phase 4 — Rhai scope reduction -Remove Rhai custom syntax registrations. Keep Rhai only as expression evaluator: -```rust -engine.eval_expression::(&expr, &scope) -``` - ---- - -## File Map - -``` -basic/ - mod.rs ← add compile_to_steps(), ExecutionPlan enum - compiler/ - mod.rs ← existing Rhai compiler, unchanged - step_compiler.rs ← NEW: BASIC → Vec - workflow/ - mod.rs ← NEW: WorkflowEngine - engine.rs ← NEW: execute_step, load/save state - variables.rs ← NEW: Variables (HashMap) - steps.rs ← NEW: Step enum - keywords/ ← existing, unchanged in Phase 1 -``` - ---- - -## Keyword Compatibility - -### Category A — Workflow steps (implement as `Step` variants) -`TALK`, `HEAR`, `SET`, `IF/ELSE/END IF`, `SEND MAIL`, `SEND TEMPLATE`, `SCHEDULE`, -`SAVE`/`INSERT`/`UPDATE`, `GET`, `FIND`, `SEARCH`, `USE KB`, `USE TOOL`, `REMEMBER`, -`HTTP GET/POST/PUT/DELETE`, `WAIT`, `TRANSFER TO HUMAN`, `CREATE TASK`, `BOOK`, `SCORE LEAD` - -### Category B — Pure expressions (Rhai as calculator, no step boundary) -`math/*`, `datetime/*`, `string_functions`, `arrays/*`, `core_functions`, `validation/*`, `FORMAT` -→ Stored as expression strings in Step, evaluated at runtime via `engine.eval_expression_with_scope()` - -### Category C — Rhai-only (scripts using these stay in Rhai mode, no `#workflow`) -`code_sandbox`, `use_website`, `face_api`, `on_change`, `on_email`, `webhook`, -`procedures` (FUNCTION/SUB/CALL), `for_next` (FOR EACH loops), `switch_case`, `events`, `orchestration` - -A script with any Category C keyword cannot use `#workflow`. The compiler detects this and errors early. - ---- - -## How Compilation Works Without Rhai - -Workflow compiler is a **line-by-line parser**, not a Rhai AST walk: - -``` -Input line → Step variant -───────────────────────────────────────────────────── -TALK "Hello ${name}" → Step::Talk { template } -HEAR description → Step::Hear { var, input_type } -SET x = score + 1 → Step::Set { var, expr: "score + 1" } -IF score > 10 THEN → Step::If { cond: "score > 10", then_steps, else_steps } -SEND MAIL to, s, b → Step::SendMail { to, subject, body } -USE TOOL path → Step::UseTool { path, args } -``` - -Expressions (`score + 1`, `score > 10`) are stored as **raw strings** in the Step struct. -At runtime, Rhai evaluates them as pure expressions — no custom syntax, no side effects: - -```rust -let mut engine = Engine::new(); // no register_custom_syntax calls -let mut scope = Scope::new(); -for (k, v) in &variables { scope.push_dynamic(k, v.clone()); } -let result = engine.eval_expression_with_scope::(&mut scope, expr)?; -``` - -Rhai remains a dependency but is used only as a math/string expression evaluator (~5 lines of code at runtime). All custom keyword machinery is bypassed entirely. - ---- - - - -| Engine | Lang | Latency | RAM | Rust SDK | Verdict | -|--------|------|---------|-----|----------|---------| -| **Custom (this plan)** | Rust | ~1ms | 0 extra | Native | ✅ Best fit | -| **[Restate](https://restate.dev)** | Rust server | ~5ms | ~50MB | ✅ official | Fallback option | -| **[Rhythm](https://github.com/maxnorth/rhythm)** | Rust | ~2ms | ~10MB | Native | Experimental | -| **Temporal** | Go+Java | ~20ms | ~500MB | ❌ | Too heavy | -| **Windmill** | Rust+TS | ~10ms | ~200MB | ❌ | Wrong abstraction | - -**Why custom over Restate:** Restate requires its own server as a proxy between HTTP requests and handlers — adds a network hop and an extra process. The custom plan uses PostgreSQL already running in the stack, zero extra infrastructure. - -**Escape hatch:** The `Step` enum in this plan maps 1:1 to Restate workflow steps. If the custom engine proves too complex to maintain, migration to Restate is mechanical — swap `WorkflowEngine::execute_step` internals, keep the compiler and Step enum unchanged. - ---- - - - -- **No re-run ever.** Steps before current_step are skipped on resume. -- **Rhai never removed entirely** — used for expression eval only. -- **Backwards compatible** — no `#workflow` = Rhai mode, existing bots unaffected. -- **HEAR in workflow mode = zero threads held.** State in DB, not RAM. -- **Tool scripts** (called by LLM) stay in Rhai mode — they're short-lived, no HEAR needed. diff --git a/restart.sh b/restart.sh index a9f3613..60ee9d1 100755 --- a/restart.sh +++ b/restart.sh @@ -4,21 +4,38 @@ set -e echo "Stopping..." pkill -f botserver || true pkill -f botui || true +pkill -f botmodels || true pkill -f rustc || true echo "Cleaning..." -rm -f botserver.log botui.log +rm -f botserver.log botui.log botmodels.log echo "Building..." cargo build -p botserver cargo build -p botui +echo "Starting botmodels..." +cd botmodels +source venv/bin/activate +uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 & +echo " PID: $!" +cd .. + +echo "Waiting for botmodels..." +for i in $(seq 1 30); do + if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then + echo " botmodels ready" + break + fi + sleep 1 +done + echo "Starting botserver..." -RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 & +BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=debug ./target/debug/botserver --noconsole > botserver.log 2>&1 & echo " PID: $!" echo "Starting botui..." BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > botui.log 2>&1 & echo " PID: $!" -echo "Done. Logs: tail -f botserver.log botui.log" +echo "Done. Logs: tail -f botserver.log botui.log botmodels.log" diff --git a/usekb2.md b/usekb2.md new file mode 100644 index 0000000..027b301 --- /dev/null +++ b/usekb2.md @@ -0,0 +1,154 @@ +# USE KB 2.0: Group-Based Knowledge Base Access + +## Overview +Modify the USE KB keyword to respect user group permissions, ensuring that THINK KB queries only return answers from knowledge base folders that belong to groups the logged-in user is a member of. + +## Current Architecture + +### USE KB Flow +1. User executes `USE KB "kb_name"` in BASIC script +2. `use_kb.rs:add_kb_to_session()` checks if KB exists in `kb_collections` +3. Creates default KB entry if not found +4. Adds association to `session_kb_associations` table +5. KB becomes active for the session + +### THINK KB Flow +1. User executes `THINK KB "query"` +2. `think_kb.rs:think_kb_search()` gets all active KBs from `session_kb_associations` +3. For each active KB, calls `KnowledgeBaseManager.search()` on its Qdrant collection +4. Returns combined results from all active KBs + +### Group System +- Groups stored in `rbac_groups` table +- User membership in `rbac_user_groups` table +- Group permissions via `rbac_group_roles` table + +## Proposed Changes + +### 1. Database Schema Changes + +Add new table `kb_group_associations`: + +```sql +CREATE TABLE kb_group_associations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + kb_id UUID NOT NULL REFERENCES kb_collections(id) ON DELETE CASCADE, + group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE, + granted_by UUID REFERENCES users(id) ON DELETE SET NULL, + granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(kb_id, group_id) +); +``` + +Migration file: `botserver/migrations/6.2.0-01-kb-groups/up.sql` + +### 2. Backend Logic Changes + +#### Modify `think_kb_search()` in `think_kb.rs` +- Add user group lookup before searching +- Filter active KBs to only those accessible by user's groups +- Allow access if KB has no group associations (public KBs) OR user is in associated groups + +```rust +async fn think_kb_search( + kb_manager: Arc, + db_pool: DbPool, + session_id: Uuid, + bot_id: Uuid, + user_id: Uuid, // Add user_id parameter + query: &str, +) -> Result { + // Get user's groups + let user_groups = get_user_groups(&db_pool, user_id)?; + + // Get active KBs filtered by groups + let accessible_kbs = get_accessible_kbs_for_session(&db_pool, session_id, &user_groups)?; + + // Search only accessible KBs + // ... rest of search logic +} +``` + +#### Add `get_accessible_kbs_for_session()` function +```rust +fn get_accessible_kbs_for_session( + conn_pool: &DbPool, + session_id: Uuid, + user_groups: &[String], +) -> Result, String> { + // Query that joins session_kb_associations with kb_group_associations + // Returns KBs where group_id IS NULL (public) OR group_id IN user_groups +} +``` + +#### Modify `add_kb_to_session()` in `use_kb.rs` +- Add optional group access check +- Allow USE KB if user has access to the KB's groups + +### 3. API Changes + +Add new endpoints in `rbac.rs` for KB-group management: + +```rust +// Assign KB to group +POST /api/rbac/kbs/{kb_id}/groups/{group_id} + +// Remove KB from group +DELETE /api/rbac/kbs/{kb_id}/groups/{group_id} + +// Get groups for KB +GET /api/rbac/kbs/{kb_id}/groups + +// Get KBs accessible by user +GET /api/rbac/users/{user_id}/accessible-kbs +``` + +### 4. Frontend Changes + +#### Update `botui/ui/suite/admin/groups.html` +- Add "Knowledge Bases" tab to group detail panel +- Show list of KBs assigned to the group +- Allow adding/removing KB assignments + +#### Update `botui/ui/suite/drive/drive.html` +- Add group visibility indicators for KB folders +- Show which groups have access to each KB + +### 5. Migration Strategy + +1. Create new migration for `kb_group_associations` table +2. Run migration to create table +3. Assign existing KBs to default groups (e.g., "all_users" group) +4. Update application code +5. Deploy and test + +### 6. Backward Compatibility + +- Existing KBs without group associations remain public +- Existing USE KB calls continue to work +- THINK KB will filter results based on new permissions + +## Implementation Steps + +1. ✅ Database migration for kb_group_associations +2. ✅ Modify think_kb_search to accept user_id and filter by groups +3. ✅ Update THINK KB keyword registration to pass user_id +4. ✅ Add group access check to USE KB +5. ✅ Add API endpoints for KB-group management +6. ✅ Update admin UI for group-KB assignment +7. ✅ Update drive UI to show group access +8. ✅ Add tests for group-based access control + +## Security Considerations + +- All KB access checks must happen at the database level +- No client-side filtering of search results +- Group membership verified on each request +- Audit logging for KB access attempts + +## Testing + +- Unit tests for group access functions +- Integration tests for THINK KB with group filtering +- UI tests for admin group-KB management +- End-to-end tests with different user group scenarios \ No newline at end of file