feat: add researchowl
This commit is contained in:
@@ -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
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user