From 65f93b745d752a8314111533a9435bb8fd19b20b Mon Sep 17 00:00:00 2001 From: chemavx Date: Mon, 27 Apr 2026 13:53:42 +0000 Subject: [PATCH] feat: add researchowl --- backup-system/cronjob-backup-verify.yaml | 44 +++++++++ n8n/health-check-code-node.js | 108 +++++++++++++++++++++++ researchowl/argocd-app.yaml | 20 +++++ researchowl/deployment.yaml | 96 ++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 backup-system/cronjob-backup-verify.yaml create mode 100644 n8n/health-check-code-node.js create mode 100644 researchowl/argocd-app.yaml create mode 100644 researchowl/deployment.yaml diff --git a/backup-system/cronjob-backup-verify.yaml b/backup-system/cronjob-backup-verify.yaml new file mode 100644 index 0000000..df632b2 --- /dev/null +++ b/backup-system/cronjob-backup-verify.yaml @@ -0,0 +1,44 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backup-verify + namespace: backup-system +spec: + schedule: "0 4 * * 1" + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 3 + successfulJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + spec: + nodeSelector: + kubernetes.io/hostname: chemavx-k8 + restartPolicy: OnFailure + securityContext: + runAsUser: 0 + containers: + - name: verify + image: rclone/rclone:latest + command: ["/bin/sh", "/scripts/verify.sh"] + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + volumeMounts: + - mountPath: /rclone/rclone.conf + name: rclone-conf + - mountPath: /scripts + name: scripts + volumes: + - name: rclone-conf + hostPath: + path: /home/chemavx/.config/rclone/rclone.conf + type: File + - name: scripts + hostPath: + path: /data/backups/scripts + type: Directory diff --git a/n8n/health-check-code-node.js b/n8n/health-check-code-node.js new file mode 100644 index 0000000..57df6fd --- /dev/null +++ b/n8n/health-check-code-node.js @@ -0,0 +1,108 @@ +// n8n Code node — Health Check +// Usa https de Node.js (NODE_FUNCTION_ALLOW_BUILTIN=https) +// Patrón async IIFE: compatible con n8n 2.x task runner + +const https = require('https'); + +return await (async () => { + + // ── Configuración ──────────────────────────────────────────────────────── + + const TELEGRAM_TOKEN = '8611913802:AAFlrFtc0vYISOliO_W8B4c-W1ue0hG9Fio'; + const TELEGRAM_CHAT = '5138407666'; + const RESTART_WEBHOOK = 'https://n8n.chemavx.xyz/webhook/uptime-kuma-restart'; + + const SERVICES = [ + { name: 'n8n', url: 'https://n8n.chemavx.xyz' }, + { name: 'openclaw', url: 'https://openclaw.chemavx.xyz' }, + { name: 'vaultwarden', url: 'https://vaultwarden.chemavx.xyz' }, + { name: 'grafana', url: 'https://grafana.chemavx.xyz' }, + { name: 'uptime', url: 'https://uptime.chemavx.xyz' }, + { name: 'auth', url: 'https://auth.chemavx.xyz' }, + { name: 'home', url: 'https://home.chemavx.xyz' }, + { name: 'git', url: 'https://git.chemavx.xyz' }, + { name: 'argocd', url: 'https://argocd.chemavx.xyz' }, + { name: 'polymarket', url: 'https://polymarket.chemavx.xyz' }, + { name: 'ollama', url: 'https://ollama.chemavx.xyz/api/tags' }, + ]; + + // ── Helpers ────────────────────────────────────────────────────────────── + + const checkService = (service) => { + return new Promise((resolve) => { + const req = https.get(service.url, (res) => { + resolve({ name: service.name, url: service.url, ok: res.statusCode < 400, status: res.statusCode }); + res.resume(); + }); + req.on('error', (err) => { + resolve({ name: service.name, url: service.url, ok: false, status: 0, reason: err.message }); + }); + req.setTimeout(10000, () => { + req.destroy(); + resolve({ name: service.name, url: service.url, ok: false, status: 0, reason: 'timeout' }); + }); + }); + }; + + const httpsPost = (urlStr, body) => { + return new Promise((resolve) => { + const data = JSON.stringify(body); + const u = new URL(urlStr); + const options = { + hostname: u.hostname, + path: u.pathname + u.search, + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }, + }; + const req = https.request(options, (res) => { + res.resume(); + resolve(); + }); + req.on('error', () => resolve()); + req.setTimeout(10000, () => { req.destroy(); resolve(); }); + req.write(data); + req.end(); + }); + }; + + const sendTelegram = (text) => { + return httpsPost( + 'https://api.telegram.org/bot' + TELEGRAM_TOKEN + '/sendMessage', + { chat_id: TELEGRAM_CHAT, text, parse_mode: 'Markdown' } + ); + }; + + const triggerRestart = (failedServices) => { + return httpsPost(RESTART_WEBHOOK, { failed: failedServices }); + }; + + // ── Main ───────────────────────────────────────────────────────────────── + + const results = await Promise.all(SERVICES.map(checkService)); + const failed = results.filter(r => !r.ok); + const passed = results.filter(r => r.ok); + + if (failed.length > 0) { + const lines = failed + .map(s => '• *' + s.name + '*: ' + (s.reason || ('HTTP ' + s.status)) + ' (' + s.url + ')') + .join('\n'); + + const message = '⚠️ *Health Check Failed*\n\n' + lines + '\n\n_Auto-restart triggered_'; + + await sendTelegram(message); + await triggerRestart(failed); + } + + // ── Return ─────────────────────────────────────────────────────────────── + + return [{ + json: { + checked: results.length, + passed: passed.length, + failed: failed.length, + allOk: failed.length === 0, + results, + }, + }]; + +})(); diff --git a/researchowl/argocd-app.yaml b/researchowl/argocd-app.yaml new file mode 100644 index 0000000..42b9018 --- /dev/null +++ b/researchowl/argocd-app.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: researchowl + namespace: argocd +spec: + project: default + source: + repoURL: https://git.chemavx.xyz/chemavx/k8s-manifests + targetRevision: HEAD + path: researchowl + destination: + server: https://kubernetes.default.svc + namespace: researchowl + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/researchowl/deployment.yaml b/researchowl/deployment.yaml new file mode 100644 index 0000000..eba91da --- /dev/null +++ b/researchowl/deployment.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: researchowl + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: researchowl-data + namespace: researchowl +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: researchowl + namespace: researchowl + labels: + app: researchowl +spec: + replicas: 1 + selector: + matchLabels: + app: researchowl + template: + metadata: + labels: + app: researchowl + spec: + containers: + - name: researchowl + image: git.chemavx.xyz/chemavx/researchowl:latest + imagePullPolicy: Always + env: + - name: TELEGRAM_BOT_TOKEN + valueFrom: + secretKeyRef: + name: researchowl-secrets + key: telegram-bot-token + - name: TELEGRAM_ALLOWED_USERS + valueFrom: + secretKeyRef: + name: researchowl-secrets + key: telegram-allowed-users + - name: OLLAMA_URL + value: "http://ollama.chemavx.xyz" + - name: OLLAMA_MODEL + value: "qwen2.5:3b" + - name: DB_PATH + value: "/data/researchowl.db" + - name: MAX_SOURCES + value: "150" + - name: MAX_DEPTH + value: "3" + - name: QUALITY_THRESHOLD + value: "0.4" + volumeMounts: + - name: data + mountPath: /data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "500m" + volumes: + - name: data + persistentVolumeClaim: + claimName: researchowl-data + imagePullSecrets: + - name: gitea-registry + +--- +# Secret template — fill with real values and apply manually +# kubectl create secret generic researchowl-secrets \ +# --from-literal=telegram-bot-token=YOUR_TOKEN \ +# --from-literal=telegram-allowed-users=YOUR_USER_ID \ +# -n researchowl +apiVersion: v1 +kind: Secret +metadata: + name: researchowl-secrets + namespace: researchowl +type: Opaque +stringData: + telegram-bot-token: "REPLACE_ME" + telegram-allowed-users: "REPLACE_ME"