chore: sincroniza alterações locais
This commit is contained in:
parent
c3249e523d
commit
c2050f311a
7 changed files with 362 additions and 26 deletions
50
.github/workflows/ci-cd-web-desktop.yml
vendored
50
.github/workflows/ci-cd-web-desktop.yml
vendored
|
|
@ -206,6 +206,34 @@ jobs:
|
||||||
echo "Forcing service restart..."
|
echo "Forcing service restart..."
|
||||||
docker service update --force sistema_convex_backend || true
|
docker service update --force sistema_convex_backend || true
|
||||||
|
|
||||||
|
- name: Smoke test — register + heartbeat
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# Load MACHINE_PROVISIONING_SECRET from production .env on the host
|
||||||
|
if [ -f /srv/apps/sistema/.env ]; then
|
||||||
|
set -o allexport
|
||||||
|
. /srv/apps/sistema/.env
|
||||||
|
set +o allexport
|
||||||
|
fi
|
||||||
|
if [ -z "${MACHINE_PROVISIONING_SECRET:-}" ]; then
|
||||||
|
echo "MACHINE_PROVISIONING_SECRET ausente — pulando smoke test"; exit 0
|
||||||
|
fi
|
||||||
|
HOSTNAME_TEST="ci-smoke-$(date +%s)"
|
||||||
|
BODY='{"provisioningSecret":"'"$MACHINE_PROVISIONING_SECRET"'","tenantId":"tenant-atlas","hostname":"'"$HOSTNAME_TEST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventory":{"cpu":"i7","ramGb":16}},"registeredBy":"ci-smoke"}'
|
||||||
|
HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$BODY" https://tickets.esdrasrenan.com.br/api/machines/register || true)
|
||||||
|
echo "Register HTTP=$HTTP"
|
||||||
|
if [ "$HTTP" != "201" ]; then
|
||||||
|
echo "Register failed:"; tail -c 600 resp.json || true; exit 1; fi
|
||||||
|
TOKEN=$(node -e 'try{const j=require("fs").readFileSync("resp.json","utf8");process.stdout.write(JSON.parse(j).machineToken||"");}catch(e){process.stdout.write("")}' )
|
||||||
|
if [ -z "$TOKEN" ]; then echo "Missing token in register response"; exit 1; fi
|
||||||
|
HB=$(curl -sS -o /dev/null -w "%{http_code}" -H 'Content-Type: application/json' -d '{"machineToken":"'"$TOKEN"'","status":"online","metrics":{"cpuPct":5,"memFreePct":70}}' https://tickets.esdrasrenan.com.br/api/machines/heartbeat || true)
|
||||||
|
echo "Heartbeat HTTP=$HB"
|
||||||
|
if [ "$HB" != "200" ]; then echo "Heartbeat failed"; exit 1; fi
|
||||||
|
|
||||||
|
- name: Cleanup old build workdirs (keep last 3)
|
||||||
|
run: |
|
||||||
|
ls -1dt $HOME/apps/sistema.build.* 2>/dev/null | tail -n +4 | xargs -r rm -rf || true
|
||||||
|
|
||||||
- name: Restart web service with new code
|
- name: Restart web service with new code
|
||||||
run: |
|
run: |
|
||||||
docker service update --force sistema_web || true
|
docker service update --force sistema_web || true
|
||||||
|
|
@ -261,26 +289,24 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }}
|
CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }}
|
||||||
CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }}
|
CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }}
|
||||||
|
MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }}
|
||||||
|
MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }}
|
||||||
|
FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
# Load production values from /srv (do not copy .env to workspace)
|
|
||||||
if [ -f /srv/apps/sistema/.env ]; then
|
|
||||||
set -o allexport
|
|
||||||
. /srv/apps/sistema/.env
|
|
||||||
set +o allexport
|
|
||||||
fi
|
|
||||||
docker run --rm -i \
|
docker run --rm -i \
|
||||||
-v "$EFFECTIVE_APP_DIR":/app \
|
-v "$EFFECTIVE_APP_DIR":/app \
|
||||||
-w /app \
|
-w /app \
|
||||||
-e CONVEX_SELF_HOSTED_URL \
|
-e CONVEX_SELF_HOSTED_URL \
|
||||||
-e CONVEX_SELF_HOSTED_ADMIN_KEY \
|
-e CONVEX_SELF_HOSTED_ADMIN_KEY \
|
||||||
-e MACHINE_PROVISIONING_SECRET="${MACHINE_PROVISIONING_SECRET:-}" \
|
-e MACHINE_PROVISIONING_SECRET \
|
||||||
-e MACHINE_TOKEN_TTL_MS="${MACHINE_TOKEN_TTL_MS:-}" \
|
-e MACHINE_TOKEN_TTL_MS \
|
||||||
-e FLEET_SYNC_SECRET="${FLEET_SYNC_SECRET:-}" \
|
-e FLEET_SYNC_SECRET \
|
||||||
node:20-bullseye bash -lc "set -euo pipefail; unset CONVEX_DEPLOYMENT; corepack enable; corepack prepare pnpm@9 --activate; pnpm install --frozen-lockfile --prod=false; \
|
node:20-bullseye bash -lc "set -euo pipefail; unset CONVEX_DEPLOYMENT; corepack enable; corepack prepare pnpm@9 --activate; pnpm install --frozen-lockfile --prod=false; \
|
||||||
if [ -n \"\${MACHINE_PROVISIONING_SECRET:-}\" ]; then pnpm exec convex env set MACHINE_PROVISIONING_SECRET \"\${MACHINE_PROVISIONING_SECRET}\" -y; fi; \
|
if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then pnpm exec convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\" -y; fi; \
|
||||||
if [ -n \"\${MACHINE_TOKEN_TTL_MS:-}\" ]; then pnpm exec convex env set MACHINE_TOKEN_TTL_MS \"\${MACHINE_TOKEN_TTL_MS}\" -y; fi; \
|
if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then pnpm exec convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\" -y; fi; \
|
||||||
if [ -n \"\${FLEET_SYNC_SECRET:-}\" ]; then pnpm exec convex env set FLEET_SYNC_SECRET \"\${FLEET_SYNC_SECRET}\" -y; fi;"
|
if [ -n \"$FLEET_SYNC_SECRET\" ]; then pnpm exec convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\" -y; fi; \
|
||||||
|
pnpm exec convex env list"
|
||||||
|
|
||||||
- name: Ensure .env is not present for Convex deploy
|
- name: Ensure .env is not present for Convex deploy
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
51
apps/desktop/src-tauri/Cargo.lock
generated
51
apps/desktop/src-tauri/Cargo.lock
generated
|
|
@ -61,6 +61,7 @@ name = "appsdesktop"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"get_if_addrs",
|
||||||
"hostname",
|
"hostname",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|
@ -372,6 +373,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "c_linked_list"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-rs"
|
name = "cairo-rs"
|
||||||
version = "0.18.5"
|
version = "0.18.5"
|
||||||
|
|
@ -801,7 +808,7 @@ dependencies = [
|
||||||
"dlopen2_derive",
|
"dlopen2_derive",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1139,6 +1146,12 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gcc"
|
||||||
|
version = "0.3.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gdk"
|
name = "gdk"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
|
|
@ -1248,6 +1261,28 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "get_if_addrs"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7"
|
||||||
|
dependencies = [
|
||||||
|
"c_linked_list",
|
||||||
|
"get_if_addrs-sys",
|
||||||
|
"libc",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "get_if_addrs-sys"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48"
|
||||||
|
dependencies = [
|
||||||
|
"gcc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
|
@ -1321,7 +1356,7 @@ dependencies = [
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1975,7 +2010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2182,7 +2217,7 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4542,7 +4577,7 @@ checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4927,6 +4962,12 @@ dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ tauri-plugin-opener = "2"
|
||||||
tauri-plugin-store = "2.4"
|
tauri-plugin-store = "2.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
sysinfo = { version = "0.31", default-features = false, features = ["multithread", "network", "system"] }
|
sysinfo = { version = "0.31", default-features = false, features = ["multithread", "network", "system", "disk"] }
|
||||||
|
get_if_addrs = "0.5"
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use chrono::{DateTime, Utc};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
use sysinfo::{Networks, System};
|
use sysinfo::{Networks, System};
|
||||||
use tauri::async_runtime::{self, JoinHandle};
|
use tauri::async_runtime::{self, JoinHandle};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
@ -100,6 +101,181 @@ fn collect_mac_addresses() -> Vec<String> {
|
||||||
macs
|
macs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn collect_serials_platform() -> Vec<String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for path in [
|
||||||
|
"/sys/class/dmi/id/product_uuid",
|
||||||
|
"/sys/class/dmi/id/product_serial",
|
||||||
|
"/sys/class/dmi/id/board_serial",
|
||||||
|
"/etc/machine-id",
|
||||||
|
] {
|
||||||
|
if let Ok(raw) = std::fs::read_to_string(path) {
|
||||||
|
let s = raw.trim().to_string();
|
||||||
|
if !s.is_empty() && !out.contains(&s) {
|
||||||
|
out.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
fn collect_serials_platform() -> Vec<String> {
|
||||||
|
// Fase 1: sem coleta nativa; será implementada via WMI/ioreg na fase 2.
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_serials() -> Vec<String> {
|
||||||
|
collect_serials_platform()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_network_addrs() -> Vec<serde_json::Value> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
if let Ok(ifaces) = get_if_addrs::get_if_addrs() {
|
||||||
|
for iface in ifaces {
|
||||||
|
let name = iface.name;
|
||||||
|
let mac = iface.mac.map(|m| m.to_string());
|
||||||
|
let addr = iface.ip();
|
||||||
|
let ip = addr.to_string();
|
||||||
|
entries.push(json!({
|
||||||
|
"name": name,
|
||||||
|
"mac": mac,
|
||||||
|
"ip": ip,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_disks(system: &System) -> Vec<serde_json::Value> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for d in system.disks() {
|
||||||
|
let name = d.name().to_string_lossy().to_string();
|
||||||
|
let mount = d.mount_point().to_string_lossy().to_string();
|
||||||
|
let fs = String::from_utf8_lossy(d.file_system()).to_string();
|
||||||
|
let total = d.total_space();
|
||||||
|
let avail = d.available_space();
|
||||||
|
out.push(json!({
|
||||||
|
"name": name,
|
||||||
|
"mountPoint": mount,
|
||||||
|
"fs": fs,
|
||||||
|
"totalBytes": total,
|
||||||
|
"availableBytes": avail,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_inventory_metadata(system: &System) -> serde_json::Value {
|
||||||
|
let cpu_brand = system
|
||||||
|
.cpus()
|
||||||
|
.first()
|
||||||
|
.map(|cpu| cpu.brand().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mem_total_bytes = system.total_memory().saturating_mul(1024);
|
||||||
|
let network = collect_network_addrs();
|
||||||
|
let disks = collect_disks(system);
|
||||||
|
let mut inventory = json!({
|
||||||
|
"cpu": { "brand": cpu_brand },
|
||||||
|
"memory": { "totalBytes": mem_total_bytes },
|
||||||
|
"network": network,
|
||||||
|
"disks": disks,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// Softwares instalados (dpkg ou rpm)
|
||||||
|
let software = collect_software_linux();
|
||||||
|
if let Some(obj) = inventory.as_object_mut() {
|
||||||
|
obj.insert("software".into(), software);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serviços ativos (systemd)
|
||||||
|
let services = collect_services_linux();
|
||||||
|
if let Some(obj) = inventory.as_object_mut() {
|
||||||
|
obj.insert("services".into(), services);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json!({ "inventory": inventory })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn collect_software_linux() -> serde_json::Value {
|
||||||
|
use std::process::Command;
|
||||||
|
// Tenta dpkg-query primeiro
|
||||||
|
let dpkg = Command::new("sh")
|
||||||
|
.arg("-lc")
|
||||||
|
.arg("dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null || true")
|
||||||
|
.output();
|
||||||
|
if let Ok(out) = dpkg {
|
||||||
|
if out.status.success() {
|
||||||
|
let s = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for line in s.lines() {
|
||||||
|
let mut parts = line.split('\t');
|
||||||
|
let name = parts.next().unwrap_or("").trim();
|
||||||
|
let version = parts.next().unwrap_or("").trim();
|
||||||
|
if !name.is_empty() {
|
||||||
|
items.push(json!({"name": name, "version": version, "source": "dpkg"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json!(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback rpm
|
||||||
|
let rpm = std::process::Command::new("sh")
|
||||||
|
.arg("-lc")
|
||||||
|
.arg("rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\n' 2>/dev/null || true")
|
||||||
|
.output();
|
||||||
|
if let Ok(out) = rpm {
|
||||||
|
if out.status.success() {
|
||||||
|
let s = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for line in s.lines() {
|
||||||
|
let mut parts = line.split('\t');
|
||||||
|
let name = parts.next().unwrap_or("").trim();
|
||||||
|
let version = parts.next().unwrap_or("").trim();
|
||||||
|
if !name.is_empty() {
|
||||||
|
items.push(json!({"name": name, "version": version, "source": "rpm"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json!(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!([])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn collect_services_linux() -> serde_json::Value {
|
||||||
|
use std::process::Command;
|
||||||
|
let out = Command::new("sh")
|
||||||
|
.arg("-lc")
|
||||||
|
.arg("systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null || true")
|
||||||
|
.output();
|
||||||
|
if let Ok(out) = out {
|
||||||
|
if out.status.success() {
|
||||||
|
let s = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for line in s.lines() {
|
||||||
|
// Typical format: UNIT LOAD ACTIVE SUB DESCRIPTION
|
||||||
|
// We take UNIT and ACTIVE
|
||||||
|
let cols: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if cols.is_empty() { continue; }
|
||||||
|
let unit = cols.get(0).unwrap_or(&"");
|
||||||
|
let active = cols.get(2).copied().unwrap_or("");
|
||||||
|
if !unit.is_empty() {
|
||||||
|
items.push(json!({"name": unit, "status": active}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json!(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!([])
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_system() -> System {
|
fn collect_system() -> System {
|
||||||
let mut system = System::new_all();
|
let mut system = System::new_all();
|
||||||
system.refresh_all();
|
system.refresh_all();
|
||||||
|
|
@ -154,7 +330,7 @@ pub fn collect_profile() -> Result<MachineProfile, AgentError> {
|
||||||
let architecture = std::env::consts::ARCH.to_string();
|
let architecture = std::env::consts::ARCH.to_string();
|
||||||
|
|
||||||
let mac_addresses = collect_mac_addresses();
|
let mac_addresses = collect_mac_addresses();
|
||||||
let serials: Vec<String> = Vec::new();
|
let serials: Vec<String> = collect_serials();
|
||||||
|
|
||||||
if mac_addresses.is_empty() && serials.is_empty() {
|
if mac_addresses.is_empty() && serials.is_empty() {
|
||||||
return Err(AgentError::MissingIdentifiers);
|
return Err(AgentError::MissingIdentifiers);
|
||||||
|
|
@ -216,7 +392,7 @@ async fn post_heartbeat(base_url: &str, token: &str, status: Option<String>) ->
|
||||||
hostname: Some(hostname),
|
hostname: Some(hostname),
|
||||||
os: Some(os),
|
os: Some(os),
|
||||||
metrics: Some(metrics),
|
metrics: Some(metrics),
|
||||||
metadata: None,
|
metadata: Some(build_inventory_metadata(&system)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!("{}/api/machines/heartbeat", base_url);
|
let url = format!("{}/api/machines/heartbeat", base_url);
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,41 @@ docker run --rm -it \
|
||||||
Observação
|
Observação
|
||||||
- Sempre que alterar código em `convex/`, repita o comando acima para publicar as mudanças.
|
- Sempre que alterar código em `convex/`, repita o comando acima para publicar as mudanças.
|
||||||
|
|
||||||
|
### Variáveis do Convex (importante)
|
||||||
|
As functions do Convex leem variáveis via `convex env`, não do `.env` do container.
|
||||||
|
No CI, defina os seguintes Secrets (Repo → Settings → Secrets and variables → Actions):
|
||||||
|
|
||||||
|
- `CONVEX_SELF_HOSTED_URL` — ex.: `https://convex.esdrasrenan.com.br`
|
||||||
|
- `CONVEX_SELF_HOSTED_ADMIN_KEY` — gerada por `./generate_admin_key.sh`
|
||||||
|
- `MACHINE_PROVISIONING_SECRET` — hex forte
|
||||||
|
- (opcional) `MACHINE_TOKEN_TTL_MS` — ex.: `2592000000`
|
||||||
|
- (opcional) `FLEET_SYNC_SECRET`
|
||||||
|
|
||||||
|
O job `convex_deploy` sempre roda `convex env set` com os Secrets acima antes do `convex deploy`.
|
||||||
|
Se preferir setar manualmente:
|
||||||
|
|
||||||
|
- `MACHINE_PROVISIONING_SECRET` — obrigatório para `/api/machines/register`
|
||||||
|
- (opcional) `MACHINE_TOKEN_TTL_MS`, `FLEET_SYNC_SECRET`
|
||||||
|
|
||||||
|
CLI manual (exemplo):
|
||||||
|
```
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /srv/apps/sistema:/app -w /app \
|
||||||
|
-e CONVEX_SELF_HOSTED_URL=https://convex.esdrasrenan.com.br \
|
||||||
|
-e CONVEX_SELF_HOSTED_ADMIN_KEY='COLE_A_CHAVE' \
|
||||||
|
node:20-bullseye bash -lc "set -euo pipefail; corepack enable && corepack prepare pnpm@9 --activate && pnpm i --frozen-lockfile --prod=false; \
|
||||||
|
unset CONVEX_DEPLOYMENT; \
|
||||||
|
pnpm exec convex env set MACHINE_PROVISIONING_SECRET 'seu-hex' -y; \
|
||||||
|
pnpm exec convex env list"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Smoke test pós‑deploy (CI)
|
||||||
|
O pipeline executa um teste rápido após o deploy do Web:
|
||||||
|
- Registra uma máquina fake usando `MACHINE_PROVISIONING_SECRET` do `/srv/apps/sistema/.env`
|
||||||
|
- Espera `HTTP 201` e extrai `machineToken`
|
||||||
|
- Envia `heartbeat` e espera `HTTP 200`
|
||||||
|
- Se falhar, o job é marcado como erro (evita regressões silenciosas)
|
||||||
|
|
||||||
## Seeds
|
## Seeds
|
||||||
- Dados de demonstração Convex: acesse uma vez `https://tickets.esdrasrenan.com.br/dev/seed`.
|
- Dados de demonstração Convex: acesse uma vez `https://tickets.esdrasrenan.com.br/dev/seed`.
|
||||||
- Usuários (Better Auth):
|
- Usuários (Better Auth):
|
||||||
|
|
|
||||||
55
docs/convex-self-hosted-env.md
Normal file
55
docs/convex-self-hosted-env.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
Convex Self‑Hosted — Configurar env e testar provisionamento
|
||||||
|
|
||||||
|
Pré‑requisitos
|
||||||
|
- Rodar na VPS com Docker.
|
||||||
|
- Projeto em `/srv/apps/sistema`.
|
||||||
|
- Admin Key do Convex (já obtida):
|
||||||
|
`convex-self-hosted|011c148069bd37e4a3f1c10b41b19459427a20e6d7ba81f53b659861f7658cd4985c8936e9`
|
||||||
|
|
||||||
|
1) Exportar variáveis da sessão (URL + Admin Key)
|
||||||
|
export CONVEX_SELF_HOSTED_URL="https://convex.esdrasrenan.com.br"
|
||||||
|
export CONVEX_SELF_HOSTED_ADMIN_KEY='convex-self-hosted|011c148069bd37e4a3f1c10b41b19459427a20e6d7ba81f53b659861f7658cd4985c8936e9'
|
||||||
|
|
||||||
|
2) Definir MACHINE_PROVISIONING_SECRET no Convex (obrigatório)
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /srv/apps/sistema:/app -w /app \
|
||||||
|
-e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY \
|
||||||
|
node:20-bullseye bash -lc "set -euo pipefail; \
|
||||||
|
corepack enable && corepack prepare pnpm@9 --activate && pnpm i --frozen-lockfile --prod=false; \
|
||||||
|
unset CONVEX_DEPLOYMENT; \
|
||||||
|
pnpm exec convex env set MACHINE_PROVISIONING_SECRET '71daa9ef54cb224547e378f8121ca898b614446c142a132f73c2221b4d53d7d6' -y; \
|
||||||
|
pnpm exec convex env list"
|
||||||
|
|
||||||
|
3) (Opcional) Definir MACHINE_TOKEN_TTL_MS (padrão 30 dias)
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /srv/apps/sistema:/app -w /app \
|
||||||
|
-e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY \
|
||||||
|
node:20-bullseye bash -lc "set -euo pipefail; \
|
||||||
|
corepack enable && corepack prepare pnpm@9 --activate && pnpm i --frozen-lockfile --prod=false; \
|
||||||
|
unset CONVEX_DEPLOYMENT; \
|
||||||
|
pnpm exec convex env set MACHINE_TOKEN_TTL_MS '2592000000' -y; \
|
||||||
|
pnpm exec convex env list"
|
||||||
|
|
||||||
|
4) (Opcional) Definir FLEET_SYNC_SECRET
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /srv/apps/sistema:/app -w /app \
|
||||||
|
-e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY \
|
||||||
|
node:20-bullseye bash -lc "set -euo pipefail; \
|
||||||
|
corepack enable && corepack prepare pnpm@9 --activate && pnpm i --frozen-lockfile --prod=false; \
|
||||||
|
unset CONVEX_DEPLOYMENT; \
|
||||||
|
pnpm exec convex env set FLEET_SYNC_SECRET '' -y; \
|
||||||
|
pnpm exec convex env list"
|
||||||
|
|
||||||
|
5) Testar registro (gera machineToken) — substitua o hostname se quiser
|
||||||
|
HOST="vm-teste-$(date +%s)"; \
|
||||||
|
curl -sS -o resp.json -w "%{http_code}\n" -X POST 'https://tickets.esdrasrenan.com.br/api/machines/register' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"provisioningSecret":"71daa9ef54cb224547e378f8121ca898b614446c142a132f73c2221b4d53d7d6","tenantId":"tenant-atlas","hostname":"'"$HOST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventario":{"cpu":"i7","ramGb":16}},"registeredBy":"manual-test"}'; \
|
||||||
|
echo; tail -c 400 resp.json || true
|
||||||
|
|
||||||
|
6) (Opcional) Enviar heartbeat com o token retornado
|
||||||
|
TOKEN=$(node -e 'try{const j=require("fs").readFileSync("resp.json","utf8");process.stdout.write(JSON.parse(j).machineToken||"");}catch(e){process.stdout.write("")}' ); \
|
||||||
|
[ -n "$TOKEN" ] && curl -sS -o /dev/null -w "%{http_code}\n" -X POST 'https://tickets.esdrasrenan.com.br/api/machines/heartbeat' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"machineToken":"'"$TOKEN"'","status":"online","metrics":{"cpuPct":12,"memFreePct":61}}'
|
||||||
|
|
||||||
|
|
@ -184,10 +184,10 @@ export function AdminMachinesOverview({ tenantId }: { tenantId: string }) {
|
||||||
{machines.length === 0 ? (
|
{machines.length === 0 ? (
|
||||||
<EmptyState />
|
<EmptyState />
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto max-h-[70vh] overflow-y-auto rounded-md border border-slate-200">
|
||||||
<Table>
|
<Table className="">
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 z-10 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<TableRow>
|
<TableRow className="border-slate-200">
|
||||||
<TableHead>Hostname</TableHead>
|
<TableHead>Hostname</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
<TableHead>Último heartbeat</TableHead>
|
<TableHead>Último heartbeat</TableHead>
|
||||||
|
|
@ -209,8 +209,10 @@ export function AdminMachinesOverview({ tenantId }: { tenantId: string }) {
|
||||||
<div className="font-medium">{machine.hostname}</div>
|
<div className="font-medium">{machine.hostname}</div>
|
||||||
<p className="text-xs text-muted-foreground">{machine.authEmail ?? "—"}</p>
|
<p className="text-xs text-muted-foreground">{machine.authEmail ?? "—"}</p>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="space-y-1">
|
<TableCell className="align-middle">
|
||||||
|
<div className="flex items-center">
|
||||||
<MachineStatusBadge status={machine.status} />
|
<MachineStatusBadge status={machine.status} />
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue