// "use node"; // import { action } from "../_generated/server"; // import { v } from "convex/values"; // import { api, internal } from "../_generated/api"; // import { Client } from "ssh2"; // import { readFileSync } from "fs"; // import { decryptSMTPPasswordNode } from "./utils/nodeCrypto"; // /** // * Interface para configuração SSH // */ // interface SSHConfig { // host: string; // port: number; // username: string; // password?: string; // keyPath?: string; // } // /** // * Executar comando via SSH // */ // async function executarComandoSSH( // config: SSHConfig, // comando: string // ): Promise<{ sucesso: boolean; output: string; erro?: string }> { // return new Promise((resolve) => { // const conn = new Client(); // let output = ""; // let errorOutput = ""; // conn.on("ready", () => { // conn.exec(comando, (err, stream) => { // if (err) { // conn.end(); // resolve({ sucesso: false, output: "", erro: err.message }); // return; // } // stream // .on("close", (code: number | null, signal: string | null) => { // conn.end(); // if (code === 0) { // resolve({ sucesso: true, output: output.trim() }); // } else { // resolve({ // sucesso: false, // output: output.trim(), // erro: `Comando retornou código ${code}${signal ? ` (signal: ${signal})` : ""}. ${errorOutput}`, // }); // } // }) // .on("data", (data: Buffer) => { // output += data.toString(); // }) // .stderr.on("data", (data: Buffer) => { // errorOutput += data.toString(); // }); // }); // }).on("error", (err) => { // resolve({ sucesso: false, output: "", erro: err.message }); // }).connect({ // host: config.host, // port: config.port, // username: config.username, // password: config.password, // privateKey: config.keyPath ? readFileSync(config.keyPath) : undefined, // readyTimeout: 10000, // }); // }); // } // /** // * Ler arquivo via SSH // */ // async function lerArquivoSSH( // config: SSHConfig, // caminho: string // ): Promise<{ sucesso: boolean; conteudo?: string; erro?: string }> { // const comando = `cat "${caminho}" 2>&1`; // const resultado = await executarComandoSSH(config, comando); // if (!resultado.sucesso) { // return { sucesso: false, erro: resultado.erro || "Erro ao ler arquivo" }; // } // return { sucesso: true, conteudo: resultado.output }; // } // /** // * Escrever arquivo via SSH // */ // async function escreverArquivoSSH( // config: SSHConfig, // caminho: string, // conteudo: string // ): Promise<{ sucesso: boolean; erro?: string }> { // // Escapar conteúdo para shell // const conteudoEscapado = conteudo // .replace(/\\/g, "\\\\") // .replace(/"/g, '\\"') // .replace(/\$/g, "\\$") // .replace(/`/g, "\\`"); // const comando = `cat > "${caminho}" << 'JITSI_CONFIG_EOF' // ${conteudo} // JITSI_CONFIG_EOF`; // const resultado = await executarComandoSSH(config, comando); // if (!resultado.sucesso) { // return { sucesso: false, erro: resultado.erro || "Erro ao escrever arquivo" }; // } // return { sucesso: true }; // } // /** // * Aplicar configurações do Jitsi no servidor Docker via SSH // */ // export const aplicarConfiguracaoServidor = action({ // args: { // configId: v.id("configuracaoJitsi"), // sshPassword: v.optional(v.string()), // Senha SSH (se não usar chave) // }, // returns: v.union( // v.object({ // sucesso: v.literal(true), // mensagem: v.string(), // detalhes: v.optional(v.string()), // }), // v.object({ sucesso: v.literal(false), erro: v.string() }) // ), // handler: async (ctx, args): Promise< // | { sucesso: true; mensagem: string; detalhes?: string } // | { sucesso: false; erro: string } // > => { // try { // // Buscar configuração // const config = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsi, {}); // if (!config || config._id !== args.configId) { // return { sucesso: false as const, erro: "Configuração não encontrada" }; // } // // Verificar se tem configurações SSH // const configFull = await ctx.runQuery(api.configuracaoJitsi.obterConfigJitsiCompleta, { // configId: args.configId, // }); // if (!configFull || !configFull.sshHost) { // return { // sucesso: false as const, // erro: "Configurações SSH não estão definidas. Configure o servidor SSH primeiro.", // }; // } // // Configurar SSH // let sshPasswordDecrypted: string | undefined = undefined; // // Se senha foi fornecida, usar ela. Caso contrário, tentar descriptografar a armazenada // if (args.sshPassword) { // sshPasswordDecrypted = args.sshPassword; // } else if (configFull.sshPasswordHash && configFull.sshPasswordHash !== "********") { // // Tentar descriptografar senha armazenada // try { // sshPasswordDecrypted = await decryptSMTPPasswordNode(configFull.sshPasswordHash); // } catch (error) { // return { // sucesso: false as const, // erro: "Não foi possível descriptografar a senha SSH armazenada. Forneça a senha novamente.", // }; // } // } // const sshConfig: SSHConfig = { // host: configFull.sshHost, // port: configFull.sshPort || 22, // username: configFull.sshUsername || "", // password: sshPasswordDecrypted, // keyPath: configFull.sshKeyPath || undefined, // }; // if (!sshConfig.username) { // return { sucesso: false as const, erro: "Usuário SSH não configurado" }; // } // if (!sshConfig.password && !sshConfig.keyPath) { // return { // sucesso: false as const, // erro: "Senha SSH ou caminho da chave deve ser fornecido", // }; // } // const basePath = configFull.jitsiConfigPath || "~/.jitsi-meet-cfg"; // const dockerComposePath = configFull.dockerComposePath || "."; // // Extrair host e porta do domain // const [host, portStr] = configFull.domain.split(":"); // const port = portStr ? parseInt(portStr, 10) : configFull.useHttps ? 443 : 80; // const protocol = configFull.useHttps ? "https" : "http"; // const detalhes: string[] = []; // // 1. Atualizar arquivo .env do docker-compose // if (dockerComposePath) { // const envContent = `# Configuração Jitsi - Atualizada automaticamente pelo SGSE // CONFIG=${basePath} // TZ=America/Recife // ENABLE_LETSENCRYPT=0 // HTTP_PORT=${protocol === "https" ? 8000 : port} // HTTPS_PORT=${configFull.useHttps ? port : 8443} // PUBLIC_URL=${protocol}://${host}${portStr ? `:${port}` : ""} // DOMAIN=${host} // ENABLE_AUTH=0 // ENABLE_GUESTS=1 // ENABLE_TRANSCRIPTION=0 // ENABLE_RECORDING=0 // ENABLE_PREJOIN_PAGE=0 // START_AUDIO_MUTED=0 // START_VIDEO_MUTED=0 // ENABLE_XMPP_WEBSOCKET=0 // ENABLE_P2P=1 // MAX_NUMBER_OF_PARTICIPANTS=10 // RESOLUTION_WIDTH=1280 // RESOLUTION_HEIGHT=720 // JWT_APP_ID=${configFull.appId} // JWT_APP_SECRET= // `; // const envPath = `${dockerComposePath}/.env`; // const resultadoEnv = await escreverArquivoSSH(sshConfig, envPath, envContent); // if (!resultadoEnv.sucesso) { // return { // sucesso: false as const, // erro: `Erro ao atualizar .env: ${resultadoEnv.erro}`, // }; // } // detalhes.push(`✓ Arquivo .env atualizado: ${envPath}`); // } // // 2. Atualizar configuração do Prosody (conforme documentação oficial) // const prosodyConfigPath = `${basePath}/prosody/config/${host}.cfg.lua`; // const prosodyContent = `-- Configuração Prosody para ${host} // -- Gerada automaticamente pelo SGSE // -- Baseado na documentação oficial do Jitsi Meet // VirtualHost "${host}" // authentication = "anonymous" // modules_enabled = { // "bosh"; // "websocket"; // "ping"; // "speakerstats"; // "turncredentials"; // "presence"; // "conference_duration"; // "stats"; // } // c2s_require_encryption = false // allow_anonymous_s2s = false // bosh_max_inactivity = 60 // bosh_max_polling = 5 // bosh_max_stanzas = 5 // Component "conference.${host}" "muc" // storage = "memory" // muc_room_locking = false // muc_room_default_public_jids = true // muc_room_cache_size = 1000 // muc_log_presences = true // Component "jitsi-videobridge.${host}" // component_secret = "" // Component "focus.${host}" // component_secret = "" // `; // const resultadoProsody = await escreverArquivoSSH(sshConfig, prosodyConfigPath, prosodyContent); // if (!resultadoProsody.sucesso) { // return { // sucesso: false as const, // erro: `Erro ao atualizar Prosody: ${resultadoProsody.erro}`, // }; // } // detalhes.push(`✓ Configuração Prosody atualizada: ${prosodyConfigPath}`); // // 3. Atualizar configuração do Jicofo // const jicofoConfigPath = `${basePath}/jicofo/sip-communicator.properties`; // const jicofoContent = `# Configuração Jicofo // # Gerada automaticamente pelo SGSE // org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.${host} // org.jitsi.jicofo.jid=XMPP_USER@${host} // org.jitsi.jicofo.BRIDGE_MUC_JID=MUC_BRIDGE_JID@internal.${host} // org.jitsi.jicofo.app.ID=${configFull.appId} // `; // const resultadoJicofo = await escreverArquivoSSH(sshConfig, jicofoConfigPath, jicofoContent); // if (!resultadoJicofo.sucesso) { // return { // sucesso: false as const, // erro: `Erro ao atualizar Jicofo: ${resultadoJicofo.erro}`, // }; // } // detalhes.push(`✓ Configuração Jicofo atualizada: ${jicofoConfigPath}`); // // 4. Atualizar configuração do JVB // const jvbConfigPath = `${basePath}/jvb/sip-communicator.properties`; // const jvbContent = `# Configuração JVB (Jitsi Video Bridge) // # Gerada automaticamente pelo SGSE // org.jitsi.videobridge.AUTHORIZED_SOURCE_REGEXP=.*@${host}/.* // org.jitsi.videobridge.xmpp.user.shard.HOSTNAME=${host} // org.jitsi.videobridge.xmpp.user.shard.DOMAIN=auth.${host} // org.jitsi.videobridge.xmpp.user.shard.USERNAME=jvb // org.jitsi.videobridge.xmpp.user.shard.MUC_JIDS=JvbBrewery@internal.${host} // `; // const resultadoJvb = await escreverArquivoSSH(sshConfig, jvbConfigPath, jvbContent); // if (!resultadoJvb.sucesso) { // return { // sucesso: false as const, // erro: `Erro ao atualizar JVB: ${resultadoJvb.erro}`, // }; // } // detalhes.push(`✓ Configuração JVB atualizada: ${jvbConfigPath}`); // // 5. Reiniciar containers Docker // if (dockerComposePath) { // const resultadoRestart = await executarComandoSSH( // sshConfig, // `cd "${dockerComposePath}" && docker-compose restart 2>&1 || docker compose restart 2>&1` // ); // if (!resultadoRestart.sucesso) { // return { // sucesso: false as const, // erro: `Erro ao reiniciar containers: ${resultadoRestart.erro}`, // }; // } // detalhes.push(`✓ Containers Docker reiniciados`); // } // // Atualizar timestamp de configuração no servidor // await ctx.runMutation(internal.configuracaoJitsi.marcarConfiguradoNoServidor, { // configId: args.configId, // }); // return { // sucesso: true as const, // mensagem: "Configurações aplicadas com sucesso no servidor Jitsi", // detalhes: detalhes.join("\n"), // }; // } catch (error: unknown) { // const errorMessage = error instanceof Error ? error.message : String(error); // return { // sucesso: false as const, // erro: `Erro ao aplicar configurações: ${errorMessage}`, // }; // } // }, // }); // /** // * Testar conexão SSH // */ // export const testarConexaoSSH = action({ // args: { // sshHost: v.string(), // sshPort: v.optional(v.number()), // sshUsername: v.string(), // sshPassword: v.optional(v.string()), // sshKeyPath: v.optional(v.string()), // }, // returns: v.union( // v.object({ sucesso: v.literal(true), mensagem: v.string() }), // v.object({ sucesso: v.literal(false), erro: v.string() }) // ), // handler: async (ctx, args): Promise< // | { sucesso: true; mensagem: string } // | { sucesso: false; erro: string } // > => { // try { // if (!args.sshPassword && !args.sshKeyPath) { // return { // sucesso: false as const, // erro: "Senha SSH ou caminho da chave deve ser fornecido", // }; // } // const sshConfig: SSHConfig = { // host: args.sshHost, // port: args.sshPort || 22, // username: args.sshUsername, // password: args.sshPassword || undefined, // keyPath: args.sshKeyPath || undefined, // }; // // Tentar executar um comando simples // const resultado = await executarComandoSSH(sshConfig, "echo 'SSH_OK'"); // if (resultado.sucesso && resultado.output.includes("SSH_OK")) { // return { // sucesso: true as const, // mensagem: "Conexão SSH estabelecida com sucesso", // }; // } // return { // sucesso: false as const, // erro: resultado.erro || "Falha ao estabelecer conexão SSH", // }; // } catch (error: unknown) { // const errorMessage = error instanceof Error ? error.message : String(error); // return { // sucesso: false as const, // erro: `Erro ao testar SSH: ${errorMessage}`, // }; // } // }, // });