Compare commits
34 Commits
47acedb455
...
alpha
| Author | SHA1 | Date | |
|---|---|---|---|
| 336b7759d0 | |||
| 68c108cfe8 | |||
| 9ebd48af0c | |||
| 113ebdc7c5 | |||
| 5a70460e89 | |||
| 1228b957de | |||
| 3c373351b7 | |||
| e4962b7334 | |||
| 7f07fa5a89 | |||
| fc42bd6f94 | |||
| 17e60354a1 | |||
| 1d4dfb594b | |||
| 42bfb43b65 | |||
| bbcf656a3d | |||
| e0ebc120ee | |||
| 40e504c2fe | |||
| 34cfece0b0 | |||
| 4e8f93173e | |||
| 1fe16429a4 | |||
| ac1395486d | |||
| a07cecb6f3 | |||
| ffc9ab8ab0 | |||
| d4b367794c | |||
| 3871d04d48 | |||
| 0ed293b6ea | |||
| d5c2f6eae5 | |||
| 8e1c5f1e66 | |||
| 1965d37687 | |||
| aea8608f3d | |||
| 06c203485f | |||
| b12c143791 | |||
| d9ca093155 | |||
| 1b8c862b10 | |||
| 00f22f33af |
@@ -1,20 +1,33 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
heimdall:
|
heimdall:
|
||||||
image: lscr.io/linuxserver/heimdall:latest
|
image: lscr.io/linuxserver/heimdall:latest
|
||||||
ports:
|
ports:
|
||||||
- "85:80"
|
- target: 80
|
||||||
- "403:443"
|
published: 85
|
||||||
|
mode: ingress
|
||||||
|
- target: 443
|
||||||
|
published: 403
|
||||||
|
mode: ingress
|
||||||
environment:
|
environment:
|
||||||
- PUID=1000
|
- PUID=1000
|
||||||
- PGID=1000
|
- PGID=1000
|
||||||
- TZ=America/New_York
|
- TZ=America/New_York
|
||||||
volumes:
|
volumes:
|
||||||
- heimdall_config:/config
|
- type: bind
|
||||||
|
source: /var/lib/docker-data/heimdall/config
|
||||||
|
target: /config
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
replicas: 1
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- node.hostname == snshpd4lnxswm01
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
volumes:
|
networks:
|
||||||
heimdall_config:
|
default:
|
||||||
driver: local
|
name: homelab_public
|
||||||
driver_opts:
|
external: true
|
||||||
type: nfs
|
|
||||||
o: "addr=14.10.10.71,rw,nfsvers=4"
|
|
||||||
device: ":/volume1/docker/heimdall/"
|
|
||||||
|
|||||||
21
docker-deployments/shepherd.yml
Normal file
21
docker-deployments/shepherd.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
shepherd:
|
||||||
|
image: containrrr/shepherd:latest
|
||||||
|
environment:
|
||||||
|
- SLEEP_TIME=24h
|
||||||
|
- IGNORELIST_SERVICES=nginx-proxy-manager_app nginx-proxy-manager_backup
|
||||||
|
- UPDATE_OPTIONS=--with-registry-auth
|
||||||
|
- ROLLBACK_FAILURE=true
|
||||||
|
- TZ=America/New_York
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
replicas: 1
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- node.role == manager
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
@@ -180,3 +180,5 @@ We will define firewall groups on the UDM Pro:
|
|||||||
* Secondary: `140.44.4.65`
|
* Secondary: `140.44.4.65`
|
||||||
* Set UDM Pro WAN DNS to `140.44.4.35` & `140.44.4.65` (or Quad9 fallback).
|
* Set UDM Pro WAN DNS to `140.44.4.35` & `140.44.4.65` (or Quad9 fallback).
|
||||||
* Apply LAN IN firewall rule matrix on UDM Pro to enforce strict VLAN isolation.
|
* Apply LAN IN firewall rule matrix on UDM Pro to enforce strict VLAN isolation.
|
||||||
|
|
||||||
|
snaisvr44hrvstr0x - AppropriateBeav3r39!
|
||||||
@@ -99,12 +99,12 @@ All Kubernetes resources are hosted on the **Compute VLAN (VLAN 20 — `140.44.4
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### AI Stack (Raspberry Pis)
|
### AI Stack (Raspberry Pis with Hailo NPU)
|
||||||
* **Infrastructure role:** Edge AI inference and local web interface.
|
* **Infrastructure role:** Edge AI inference backend and Open WebUI frontend.
|
||||||
* **SSH Default User:** `snarf`
|
* **SSH Default User:** `snarf`
|
||||||
* **IP Allocation:**
|
* **IP Allocation:**
|
||||||
* `snnpud4lnxops01` $\rightarrow$ `140.44.4.7` (Raspberry Pi with AI Hat 2, arm64, Ready)
|
* `snnpud4lnxops01` $\rightarrow$ `140.44.4.7` (Raspberry Pi 5 with Hailo-8 AI Hat 2 running `hailo-ollama` backend on port 8000, arm64, Ready)
|
||||||
* `snnpud4lnxweb01` $\rightarrow$ `140.44.4.8` (Raspberry Pi running Open WebUI, arm64, Ready)
|
* `snnpud4lnxweb01` $\rightarrow$ `140.44.4.8` (Raspberry Pi running Open WebUI frontend on port 8080, arm64, Ready)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -133,11 +133,11 @@ This table records how subdomains on `*.snarfnet.net` route to internal IP addre
|
|||||||
| `mesh.snarfnet.net` | HTTPS | `140.44.4.70` (Swarm VIP) | 8086 | MeshCentral Remote Manager |
|
| `mesh.snarfnet.net` | HTTPS | `140.44.4.70` (Swarm VIP) | 8086 | MeshCentral Remote Manager |
|
||||||
| `peanut.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8050 | Peanut Dashboard |
|
| `peanut.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8050 | Peanut Dashboard |
|
||||||
| `homebox.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 3100 | Homebox Inventory Tracker |
|
| `homebox.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 3100 | Homebox Inventory Tracker |
|
||||||
|
| `vault.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8085 | VaultConfigurator 3D Modular Storage Planner |
|
||||||
| `pihole.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8041 | Legacy Swarm Pi-hole 1 |
|
| `pihole.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8041 | Legacy Swarm Pi-hole 1 |
|
||||||
| `pihole2.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8042 | Legacy Swarm Pi-hole 2 |
|
| `pihole2.snarfnet.net` | HTTP | `140.44.4.70` (Swarm VIP) | 8042 | Legacy Swarm Pi-hole 2 |
|
||||||
| `shipyard.snarfnet.net` | HTTPS | `140.44.4.71` (Swarm 01) | 9443 | Portainer Server Console |
|
| `shipyard.snarfnet.net` | HTTPS | `140.44.4.71` (Swarm 01) | 9443 | Portainer Server Console |
|
||||||
| `shipyardcontrol.snarfnet.net` | HTTPS | `140.44.4.7` (AI Node) | 443 | Turing Pi Management / Hardware Panel |
|
| `ai.snarfnet.net` | HTTP | `140.44.4.8` (`snnpud4lnxweb01`) | 8080 | Open WebUI Inference Interface |
|
||||||
| `ai.snarfnet.net` | HTTP | `140.44.4.8` (AI Web Node) | 8080 | Open WebUI Inference Interface |
|
|
||||||
| `aerodrome.snarfnet.net` | HTTPS | `140.44.4.34` (K3s VIP) | 443 | Rancher Management Console |
|
| `aerodrome.snarfnet.net` | HTTPS | `140.44.4.34` (K3s VIP) | 443 | Rancher Management Console |
|
||||||
| `grafana.snarfnet.net` | HTTP | `140.44.4.32` (K3s VIP) | 88 | Grafana Monitoring Dashboards |
|
| `grafana.snarfnet.net` | HTTP | `140.44.4.32` (K3s VIP) | 88 | Grafana Monitoring Dashboards |
|
||||||
| `authentik.snarfnet.net` | HTTPS | `140.44.4.29` (K3s VIP) | 444 | Authentik SSO Portal |
|
| `authentik.snarfnet.net` | HTTPS | `140.44.4.29` (K3s VIP) | 444 | Authentik SSO Portal |
|
||||||
|
|||||||
59
docs/opencanary_honeypot.md
Normal file
59
docs/opencanary_honeypot.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# OpenCanary Decoy Honeypot Architecture & Configuration
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
As part of the **Snarfnet Network Security Hardening Blueprint (Phase 3)**, an **OpenCanary Honeypot** has been deployed to detect lateral movement, unauthorized reconnaissance, and compromised devices inside the **Smart Home VLAN (VLAN 50, `14.4.4.0/25`)**.
|
||||||
|
|
||||||
|
* **Decoy IP:** `14.4.4.99` (Subnet: `14.4.4.0/25`, Gateway: `14.4.4.1`)
|
||||||
|
* **Host Machine:** `snshpd4lnxswm01` (`140.44.4.71`)
|
||||||
|
* **Physical Uplink:** Direct 802.1Q trunk port with tagged VLAN 50 support.
|
||||||
|
* **Network Mode:** Native Docker `macvlan` attached to subinterface `eth0.50`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Emulated Services & Ports
|
||||||
|
|
||||||
|
The honeypot presents realistic service banners designed to trap automated scanners and unauthorized intruders without exposing real systems:
|
||||||
|
|
||||||
|
| Port | Protocol | Emulated Service | Decoy Profile |
|
||||||
|
|:---:|:---:|:---|:---|
|
||||||
|
| **21** | TCP | **FTP** | Linux FTP server (`FTP server ready`) |
|
||||||
|
| **22** | TCP | **SSH** | Ubuntu OpenSSH banner (`SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10`) |
|
||||||
|
| **23** | TCP | **Telnet** | Ubuntu login prompt (`Ubuntu 22.04 LTS login: `) |
|
||||||
|
| **80** | TCP | **HTTP Web** | Synology/NAS Web Admin Login skin |
|
||||||
|
| **1433** | TCP | **MSSQL** | Microsoft SQL Server 2012 |
|
||||||
|
| **3306** | TCP | **MySQL** | MySQL 5.7 database server |
|
||||||
|
| **3389** | TCP | **RDP** | Windows Remote Desktop Protocol listener |
|
||||||
|
| **5000** | TCP | **VNC** | Remote framebuffer / VNC listener |
|
||||||
|
| **8080** | TCP | **HTTP Proxy** | Squid HTTP proxy decoy |
|
||||||
|
| **9418** | TCP | **Git** | Git daemon repository service |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Real-Time Alerting Pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Attacker["Suspicious Host / IoT Device<br>(e.g. 14.4.4.15)"] -->|TCP / Port Probe| Decoy["OpenCanary (14.4.4.99:22/80/445/3389)"]
|
||||||
|
Decoy -->|Structured JSON Log| LogFile["/opt/opencanary/logs/opencanary.log"]
|
||||||
|
LogFile -->|Real-Time Tail| Watcher["opencanary-watcher.service<br>(Python Daemon)"]
|
||||||
|
Watcher -->|HTTP POST (Priority 8)| Gotify["Gotify Server (App 6)<br>(gotify.snarfnet.net)"]
|
||||||
|
Gotify -->|Instant Push Notification| Phone["Admin Mobile App"]
|
||||||
|
Gotify -->|Discord Webhook Bridge| Discord["Discord #infra-alerts"]
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Any TCP connection, banner grab, authentication attempt, or probe arriving on `14.4.4.99` generates a structured event in `/opt/opencanary/logs/opencanary.log`.
|
||||||
|
2. The `opencanary-watcher.service` daemon ingests the event in real-time, extracts the attacker's source IP, source port, targeted port, service type, and credentials (if entered).
|
||||||
|
3. The event is posted to **Gotify Application #6** (`A9oPeNcAnArYhOnEyPoT`) with high priority (`8`).
|
||||||
|
4. `gotify-discord-bridge` formats a rich embed and posts the alert directly into the Discord `#infra-alerts` channel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Systemd Services & Maintenance
|
||||||
|
|
||||||
|
* **VLAN Subinterface Service:**
|
||||||
|
`/etc/systemd/system/opencanary-vlan.service` (Ensures `eth0.50` is brought up on boot).
|
||||||
|
* **Alert Watcher Service:**
|
||||||
|
`/etc/systemd/system/opencanary-watcher.service` (Tails log file and delivers push alerts).
|
||||||
|
* **Docker Container:**
|
||||||
|
`opencanary` (Managed via Docker with `restart: unless-stopped` on network `opencanary_net`).
|
||||||
|
|
||||||
1029
docs/snarfnet_homelab_topology.html
Normal file
1029
docs/snarfnet_homelab_topology.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/snarfnet_homelab_topology.png
Normal file
BIN
docs/snarfnet_homelab_topology.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 830 KiB |
Submodule homelab-gitops-repo updated: 9525cdbe95...5169a02440
22
scripts/add_forward_rules.py
Normal file
22
scripts/add_forward_rules.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
print(f">>> {cmd}")
|
||||||
|
_, o, e = ssh.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
if out:
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("[stderr]", err)
|
||||||
|
|
||||||
|
run("iptables -I FORWARD 1 -m state --state RELATED,ESTABLISHED -j ACCEPT")
|
||||||
|
run("iptables -I FORWARD 2 -p tcp -d 140.44.4.64 --dport 10051 -j ACCEPT")
|
||||||
|
run("iptables -L FORWARD -n -v | head -n 8")
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
58
scripts/add_kuma_bookmarks.py
Normal file
58
scripts/add_kuma_bookmarks.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
def str_presenter(dumper, data):
|
||||||
|
if '\n' in data:
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
|
||||||
|
|
||||||
|
yaml.add_representer(str, str_presenter)
|
||||||
|
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
docs = list(yaml.safe_load_all(text))
|
||||||
|
cm = docs[2]
|
||||||
|
|
||||||
|
bookmarks_yaml = """- Quick Links:
|
||||||
|
- GitHub:
|
||||||
|
- abbr: GH
|
||||||
|
href: https://github.com/
|
||||||
|
- UniFi Site Manager:
|
||||||
|
- abbr: UI
|
||||||
|
href: https://unifi.ui.com/
|
||||||
|
- Cloudflare:
|
||||||
|
- abbr: CF
|
||||||
|
href: https://dash.cloudflare.com/
|
||||||
|
- Grafana Telemetry:
|
||||||
|
- Systems Performance:
|
||||||
|
- abbr: SYS
|
||||||
|
href: https://grafana.snarfnet.net/d/snarfnet-systems/52f6861
|
||||||
|
- Container Workloads:
|
||||||
|
- abbr: K8S
|
||||||
|
href: https://grafana.snarfnet.net/d/snarfnet-containers/f09f93a6-kubernetes-containers-and-workloads
|
||||||
|
- AI Stack Intelligence:
|
||||||
|
- abbr: AI
|
||||||
|
href: https://grafana.snarfnet.net/d/snarfnet-ai-stack/snarfnet-ai-stack-and-inference-intelligence
|
||||||
|
- Uptime Status Pages:
|
||||||
|
- Unified Overview:
|
||||||
|
- abbr: ALL
|
||||||
|
href: https://uptime.snarfnet.net/status/default
|
||||||
|
- Core & VIP Fabric:
|
||||||
|
- abbr: VIP
|
||||||
|
href: https://uptime.snarfnet.net/status/core
|
||||||
|
- GitOps & CI/CD:
|
||||||
|
- abbr: OPS
|
||||||
|
href: https://uptime.snarfnet.net/status/gitops
|
||||||
|
- Edge & Ingress:
|
||||||
|
- abbr: EDG
|
||||||
|
href: https://uptime.snarfnet.net/status/edge
|
||||||
|
"""
|
||||||
|
|
||||||
|
cm['data']['bookmarks.yaml'] = bookmarks_yaml
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'w', encoding='utf-8') as f:
|
||||||
|
yaml.dump_all(docs, f, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
||||||
|
|
||||||
|
print("Updated bookmarks in homepage.yaml successfully!")
|
||||||
|
|
||||||
32
scripts/add_media_to_snarf.py
Normal file
32
scripts/add_media_to_snarf.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import json, ssl, urllib.request
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
ZABBIX_URL = 'http://140.44.4.60:8888/api_jsonrpc.php'
|
||||||
|
|
||||||
|
payload = {'jsonrpc': '2.0', 'method': 'user.login', 'params': {'username': 'Admin', 'password': 'zabbix'}, 'id': 1}
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json-rpc'})
|
||||||
|
token = json.loads(urllib.request.urlopen(req, context=ctx).read())['result']
|
||||||
|
|
||||||
|
media_payload = {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'method': 'user.update',
|
||||||
|
'params': {
|
||||||
|
'userid': '3',
|
||||||
|
'medias': [
|
||||||
|
{
|
||||||
|
'mediatypeid': '70',
|
||||||
|
'sendto': 'snarf@snarfnet.net',
|
||||||
|
'active': 0,
|
||||||
|
'severity': 63, # 1+2+4+8+16+32
|
||||||
|
'period': '1-7,00:00-24:00'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'id': 2
|
||||||
|
}
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(media_payload).encode(), headers={'Content-Type': 'application/json-rpc', 'Authorization': f'Bearer {token}'})
|
||||||
|
res = json.loads(urllib.request.urlopen(req, context=ctx).read())
|
||||||
|
print("User update result:", res)
|
||||||
26
scripts/add_topology_to_homepage.py
Normal file
26
scripts/add_topology_to_homepage.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
def str_presenter(dumper, data):
|
||||||
|
if '\n' in data:
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
|
||||||
|
|
||||||
|
yaml.add_representer(str, str_presenter)
|
||||||
|
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
docs = list(yaml.safe_load_all(text))
|
||||||
|
cm = docs[2]
|
||||||
|
|
||||||
|
services = yaml.safe_load(cm['data']['services.yaml'])
|
||||||
|
bookmarks = yaml.safe_load(cm['data']['bookmarks.yaml'])
|
||||||
|
|
||||||
|
cm['data']['services.yaml'] = yaml.dump(services, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
||||||
|
cm['data']['bookmarks.yaml'] = yaml.dump(bookmarks, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'w', encoding='utf-8') as f:
|
||||||
|
yaml.dump_all(docs, f, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
||||||
|
|
||||||
|
print("Cleaned up unicode strings in homepage.yaml!")
|
||||||
118
scripts/add_zabbix_devices.py
Normal file
118
scripts/add_zabbix_devices.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import json
|
||||||
|
import ssl
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
ZABBIX_URL = "http://140.44.4.60:8888/api_jsonrpc.php"
|
||||||
|
ZABBIX_USER = "Admin"
|
||||||
|
ZABBIX_PASS = "zabbix"
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
def zbx_call(method, params, auth=None):
|
||||||
|
payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
|
||||||
|
headers = {"Content-Type": "application/json-rpc"}
|
||||||
|
if auth:
|
||||||
|
headers["Authorization"] = f"Bearer {auth}"
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST")
|
||||||
|
with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
|
||||||
|
resp = json.loads(r.read().decode("utf-8"))
|
||||||
|
if "error" in resp:
|
||||||
|
raise RuntimeError(f"Zabbix error in {method}: {resp['error']}")
|
||||||
|
return resp["result"]
|
||||||
|
|
||||||
|
auth = zbx_call("user.login", {"username": ZABBIX_USER, "password": ZABBIX_PASS})
|
||||||
|
|
||||||
|
# Check existing host names
|
||||||
|
existing_hosts = zbx_call("host.get", {"output": ["hostid", "host", "name"]}, auth)
|
||||||
|
existing_names = {h["host"].lower(): h["hostid"] for h in existing_hosts}
|
||||||
|
|
||||||
|
# Create host groups if not exist
|
||||||
|
groups = zbx_call("hostgroup.get", {"output": ["groupid", "name"]}, auth)
|
||||||
|
group_map = {g["name"]: g["groupid"] for g in groups}
|
||||||
|
|
||||||
|
for gname in ["Network Devices", "Environmental & Sensors", "Management & IPMI"]:
|
||||||
|
if gname not in group_map:
|
||||||
|
res = zbx_call("hostgroup.create", {"name": gname}, auth)
|
||||||
|
group_map[gname] = res["groupids"][0]
|
||||||
|
print(f"Created Host Group: {gname} (ID: {group_map[gname]})")
|
||||||
|
|
||||||
|
devices_to_add = [
|
||||||
|
{
|
||||||
|
"host": "idrac-spacedock4",
|
||||||
|
"name": "Dell PowerEdge R730 Node 4 iDRAC (idrac-spacedock4)",
|
||||||
|
"ip": "140.44.44.124",
|
||||||
|
"group": "Management & IPMI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "usw-agg-core",
|
||||||
|
"name": "UniFi Switch Aggregation 10G Core",
|
||||||
|
"ip": "14.44.4.26",
|
||||||
|
"group": "Network Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "usw-24-poe-closet",
|
||||||
|
"name": "UniFi Switch 24 PoE Closet",
|
||||||
|
"ip": "14.44.4.61",
|
||||||
|
"group": "Network Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "usw-pro-24-poe-compute",
|
||||||
|
"name": "UniFi Switch Pro 24 PoE Compute",
|
||||||
|
"ip": "14.44.4.107",
|
||||||
|
"group": "Network Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "usw-flex-attic",
|
||||||
|
"name": "UniFi Switch Flex Attic",
|
||||||
|
"ip": "14.44.4.98",
|
||||||
|
"group": "Network Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "usw-flex-garage",
|
||||||
|
"name": "UniFi Switch Flex Garage",
|
||||||
|
"ip": "14.44.4.96",
|
||||||
|
"group": "Network Devices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "tempest-weather-station",
|
||||||
|
"name": "Tempest Weather Station Gateway",
|
||||||
|
"ip": "14.4.4.47",
|
||||||
|
"group": "Environmental & Sensors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "sense-energy-monitor",
|
||||||
|
"name": "Sense Home Energy Gateway",
|
||||||
|
"ip": "14.4.4.48",
|
||||||
|
"group": "Environmental & Sensors"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for dev in devices_to_add:
|
||||||
|
hname = dev["host"]
|
||||||
|
if hname.lower() in existing_names:
|
||||||
|
print(f"[EXISTS] {hname}")
|
||||||
|
continue
|
||||||
|
params = {
|
||||||
|
"host": dev["host"],
|
||||||
|
"name": dev["name"],
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"type": 1, # Agent / ICMP
|
||||||
|
"main": 1,
|
||||||
|
"useip": 1,
|
||||||
|
"ip": dev["ip"],
|
||||||
|
"dns": "",
|
||||||
|
"port": "10050"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groups": [{"groupid": group_map[dev["group"]]}],
|
||||||
|
"templates": [{"templateid": "10564"}] # ICMP Ping
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
res = zbx_call("host.create", params, auth)
|
||||||
|
print(f"[CREATED] {dev['name']} ({dev['ip']}) -> HostID {res['hostids'][0]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FAILED] {dev['name']}: {e}")
|
||||||
|
|
||||||
52
scripts/apply_and_set_pihole_pw.py
Normal file
52
scripts/apply_and_set_pihole_pw.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
def apply_and_update(ip, name, manifest_path):
|
||||||
|
print(f"\n=======================================================")
|
||||||
|
print(f"Applying to {name} ({ip})...")
|
||||||
|
with open(manifest_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(ip, username='root', key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl apply -f -")
|
||||||
|
stdin.write(content)
|
||||||
|
stdin.channel.shutdown_write()
|
||||||
|
print("Apply output:", stdout.read().decode())
|
||||||
|
err = stderr.read().decode()
|
||||||
|
if err:
|
||||||
|
print("Apply stderr:", err)
|
||||||
|
|
||||||
|
print(f"Waiting for Pi-hole deployment rollout on {name}...")
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl rollout status deployment/pihole -n dns --timeout=60s")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
# Get new pod name
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl get pods -n dns -l app=pihole -o jsonpath='{.items[0].metadata.name}'")
|
||||||
|
pod_name = stdout.read().decode().strip()
|
||||||
|
print(f"New Pi-hole pod: {pod_name}")
|
||||||
|
|
||||||
|
# Set password to snarfnetadmin
|
||||||
|
print("Setting Pi-hole password to 'snarfnetadmin'...")
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(f"k3s kubectl exec -n dns {pod_name} -- pihole setpassword snarfnetadmin")
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
print("Set password output:", out)
|
||||||
|
if err:
|
||||||
|
print("Set password stderr:", err)
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
aero_manifest = r"C:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\homelab-gitops-repo\manifests\aerodrome\dns\dns-stack.yaml"
|
||||||
|
space_manifest = r"C:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\homelab-gitops-repo\manifests\spaceport\dns\dns-stack.yaml"
|
||||||
|
|
||||||
|
apply_and_update("140.44.4.11", "Aerodrome", aero_manifest)
|
||||||
|
apply_and_update("140.44.4.41", "Spaceport", space_manifest)
|
||||||
|
print("\nDone!")
|
||||||
|
|
||||||
51
scripts/apply_bios_tuning.py
Normal file
51
scripts/apply_bios_tuning.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
|
||||||
|
targets = [
|
||||||
|
{
|
||||||
|
"name": "Node 1 (140.44.44.121)",
|
||||||
|
"ip": "140.44.44.121",
|
||||||
|
"settings": [
|
||||||
|
("bios.IntegratedDevices.SriovGlobalEnable", "Enabled")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Node 4 (140.44.44.124)",
|
||||||
|
"ip": "140.44.44.124",
|
||||||
|
"settings": [
|
||||||
|
("bios.SysProfileSettings.SysProfile", "PerfOptimized")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for t in targets:
|
||||||
|
print(f"\n==========================================")
|
||||||
|
print(f"Applying BIOS settings to {t['name']}...")
|
||||||
|
try:
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect(t['ip'], username='root', password='Katzir476!', timeout=15)
|
||||||
|
|
||||||
|
for key, val in t['settings']:
|
||||||
|
cmd = f"racadm set {key} {val}"
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
out = stdout.read().decode().strip()
|
||||||
|
print(f" racadm set {key} {val}:\n {out}")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Create BIOS job
|
||||||
|
stdin, stdout, stderr = c.exec_command("racadm jobqueue create BIOS.Setup.1-1")
|
||||||
|
job_out = stdout.read().decode().strip()
|
||||||
|
print(f" racadm jobqueue create BIOS.Setup.1-1:\n {job_out}")
|
||||||
|
|
||||||
|
# View job queue
|
||||||
|
stdin, stdout, stderr = c.exec_command("racadm jobqueue view")
|
||||||
|
q_out = stdout.read().decode().strip()
|
||||||
|
print(f" Job Queue:\n {q_out[:200]}")
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error on {t['name']}: {e}")
|
||||||
|
|
||||||
|
print("\nDone applying BIOS settings!")
|
||||||
|
|
||||||
@@ -1,38 +1,31 @@
|
|||||||
import paramiko
|
import paramiko
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding='utf-8')
|
SSH_KEY = r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet"
|
||||||
|
|
||||||
key_path = r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet"
|
def apply_manifest(host, local_file):
|
||||||
|
with open(local_file, "r") as f:
|
||||||
|
manifest_content = f.read()
|
||||||
|
|
||||||
def apply_to_cluster(ip, name, manifest_path):
|
client = paramiko.SSHClient()
|
||||||
print(f"Connecting to {name} ({ip})...")
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
client.connect(host, username="root", key_filename=SSH_KEY, timeout=10)
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
c = paramiko.SSHClient()
|
|
||||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
c.connect(ip, username="root", key_filename=key_path, timeout=10)
|
|
||||||
|
|
||||||
stdin, stdout, stderr = c.exec_command("k3s kubectl apply -f -")
|
|
||||||
stdin.write(content)
|
|
||||||
stdin.channel.shutdown_write()
|
|
||||||
|
|
||||||
out = stdout.read().decode('utf-8')
|
|
||||||
err = stderr.read().decode('utf-8')
|
|
||||||
print(f"[{name}] Apply Output:\n{out}")
|
|
||||||
if err.strip():
|
|
||||||
print(f"[{name}] Apply Error:\n{err}")
|
|
||||||
c.close()
|
|
||||||
|
|
||||||
aero_path = r"c:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\homelab-gitops-repo\manifests\aerodrome\dns\dns-stack.yaml"
|
|
||||||
space_path = r"c:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\homelab-gitops-repo\manifests\spaceport\dns\dns-stack.yaml"
|
|
||||||
|
|
||||||
apply_to_cluster("140.44.4.11", "Aerodrome", aero_path)
|
|
||||||
time.sleep(2)
|
|
||||||
apply_to_cluster("140.44.4.41", "Spaceport", space_path)
|
|
||||||
print("Finished applying manifests to both clusters!")
|
|
||||||
|
|
||||||
|
stdin, stdout, stderr = client.exec_command("k3s kubectl apply -f -")
|
||||||
|
stdin.write(manifest_content)
|
||||||
|
stdin.close()
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
client.close()
|
||||||
|
return out, err
|
||||||
|
|
||||||
|
print("=== Applying to Aerodrome (140.44.4.11) ===")
|
||||||
|
out, err = apply_manifest("140.44.4.11", r"homelab-gitops-repo\manifests\aerodrome\dns\dns-stack.yaml")
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("Stderr:", err)
|
||||||
|
|
||||||
|
print("=== Applying to Spaceport (140.44.4.41) ===")
|
||||||
|
out, err = apply_manifest("140.44.4.41", r"homelab-gitops-repo\manifests\spaceport\dns\dns-stack.yaml")
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("Stderr:", err)
|
||||||
|
|||||||
250
scripts/apply_full_homepage.py
Normal file
250
scripts/apply_full_homepage.py
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
def str_presenter(dumper, data):
|
||||||
|
if '\n' in data:
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
|
||||||
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
|
||||||
|
|
||||||
|
yaml.add_representer(str, str_presenter)
|
||||||
|
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
docs = list(yaml.safe_load_all(text))
|
||||||
|
cm = docs[2]
|
||||||
|
|
||||||
|
services_yaml = """- Critical Services:
|
||||||
|
- Authentik:
|
||||||
|
icon: authentik.png
|
||||||
|
href: https://authentik.snarfnet.net
|
||||||
|
description: SSO & Identity Management
|
||||||
|
- PeaNUT UPS:
|
||||||
|
icon: peanut.png
|
||||||
|
href: https://portal.snarfnet.net
|
||||||
|
description: UPS Power & Battery Monitor
|
||||||
|
- Unifi Controller:
|
||||||
|
icon: unifi.png
|
||||||
|
href: https://unifi.ui.com
|
||||||
|
widget:
|
||||||
|
type: unifi
|
||||||
|
url: https://140.44.44.1
|
||||||
|
username: Snarf
|
||||||
|
password: Katzir476!!!
|
||||||
|
|
||||||
|
- GitOps & Automation:
|
||||||
|
- Gitea:
|
||||||
|
icon: gitea.png
|
||||||
|
href: https://git.snarfnet.net
|
||||||
|
description: Git Repositories
|
||||||
|
widget:
|
||||||
|
type: gitea
|
||||||
|
url: https://git.snarfnet.net
|
||||||
|
key: f19e772cf54e40ceb2befe452e2bc24dd81fdb71
|
||||||
|
- ArgoCD:
|
||||||
|
icon: argocd.png
|
||||||
|
href: https://argo.snarfnet.net
|
||||||
|
description: Declarative GitOps Reconciler
|
||||||
|
widget:
|
||||||
|
type: argocd
|
||||||
|
url: https://argo.snarfnet.net
|
||||||
|
key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJob21lcGFnZTphcGlLZXkiLCJuYmYiOjE3OTQ1MDIwNzgsImlhdCI6MTc5NDUwMjA3OCwianRpIjoiZjQ2NzU0NjMtODRhOS00MjI0LWI3ODItNmY5ODQxYWEwY2UxIn0.aHhrny2RBG-vAJAyXMvan0jw65jkP3bguqEduaG78O0
|
||||||
|
- AWX Automation:
|
||||||
|
icon: awx.png
|
||||||
|
href: https://awx.snarfnet.net
|
||||||
|
description: Ansible Automation Controller
|
||||||
|
- Semaphore:
|
||||||
|
icon: semaphore.png
|
||||||
|
href: https://semaphore.snarfnet.net
|
||||||
|
description: Playbook Execution Engine
|
||||||
|
|
||||||
|
- Storage & Clusters:
|
||||||
|
- Longhorn Aerodrome:
|
||||||
|
icon: longhorn.png
|
||||||
|
href: https://longhorn.aerodrome.snarfnet.net
|
||||||
|
description: Aerodrome Distributed Flash Storage
|
||||||
|
- Longhorn Spaceport:
|
||||||
|
icon: longhorn.png
|
||||||
|
href: https://longhorn.spaceport.snarfnet.net
|
||||||
|
description: Spaceport Distributed Block Storage
|
||||||
|
- Rancher (Aerodrome):
|
||||||
|
icon: rancher.png
|
||||||
|
href: https://aerodrome.snarfnet.net
|
||||||
|
description: Kubernetes Management Console
|
||||||
|
widget:
|
||||||
|
type: customapi
|
||||||
|
url: https://aerodrome.snarfnet.net/v3/clusters/local
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer kubeconfig-u-aqzkufonck4hmbd:bw22b96nv8844k8w5r8lzpz8gnk22tjwj95j6nkzr7hd5dhm4bs6z7"
|
||||||
|
mappings:
|
||||||
|
- field: capacity.cpu
|
||||||
|
label: CPU Cores
|
||||||
|
- field: capacity.pods
|
||||||
|
label: Max Pods
|
||||||
|
- field: state
|
||||||
|
label: Cluster State
|
||||||
|
- Harvester HCI (Spacedock):
|
||||||
|
icon: harvester.png
|
||||||
|
href: https://harvester.spacedock.snarfnet.net
|
||||||
|
description: HCI Virtualization Hypervisors
|
||||||
|
widget:
|
||||||
|
type: customapi
|
||||||
|
url: https://aerodrome.snarfnet.net/v3/clusters/c-cclnt
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer kubeconfig-u-aqzkufonck4hmbd:bw22b96nv8844k8w5r8lzpz8gnk22tjwj95j6nkzr7hd5dhm4bs6z7"
|
||||||
|
mappings:
|
||||||
|
- field: capacity.cpu
|
||||||
|
label: CPU Cores
|
||||||
|
- field: capacity.pods
|
||||||
|
label: Max Pods
|
||||||
|
- field: state
|
||||||
|
label: HCI State
|
||||||
|
- Portainer Swarm:
|
||||||
|
icon: portainer.png
|
||||||
|
href: https://shipyard.snarfnet.net
|
||||||
|
description: Docker Swarm Container Fleet
|
||||||
|
widget:
|
||||||
|
type: portainer
|
||||||
|
url: https://140.44.4.71:9443
|
||||||
|
env: 3
|
||||||
|
key: ptr_TCVm8ujeB4zT0oRejyMCwKw5enaPG/uKj1KuF0uOWX8=
|
||||||
|
|
||||||
|
- Infrastructure & Observability:
|
||||||
|
- NGINX Proxy Manager:
|
||||||
|
icon: nginx-proxy-manager
|
||||||
|
href: https://nginx.snarfnet.net
|
||||||
|
description: Reverse Proxy & SSL Certificates
|
||||||
|
- Uptime Kuma:
|
||||||
|
icon: uptime-kuma
|
||||||
|
href: https://uptime.snarfnet.net
|
||||||
|
description: Multi-Endpoint Availability Probes
|
||||||
|
widget:
|
||||||
|
type: uptimekuma
|
||||||
|
url: http://140.44.4.28:448
|
||||||
|
slug: default
|
||||||
|
- Zabbix:
|
||||||
|
icon: zabbix.png
|
||||||
|
href: https://zabbix.snarfnet.net
|
||||||
|
description: Infrastructure Metrics & Alarms
|
||||||
|
- Grafana:
|
||||||
|
icon: grafana.png
|
||||||
|
href: https://grafana.snarfnet.net
|
||||||
|
description: Real-Time Metrics & AI Dashboards
|
||||||
|
widget:
|
||||||
|
type: grafana
|
||||||
|
url: https://grafana.snarfnet.net
|
||||||
|
username: admin
|
||||||
|
password: Katzir476!!!
|
||||||
|
- Core Node Glances:
|
||||||
|
icon: glances.png
|
||||||
|
href: http://140.44.4.11:61208
|
||||||
|
description: Host Resource Monitor
|
||||||
|
|
||||||
|
- Environment & Weather:
|
||||||
|
- Tempest Weather Station:
|
||||||
|
icon: weather.png
|
||||||
|
href: https://snarfhome.snarfnet.net
|
||||||
|
description: Backyard Weather Telemetry
|
||||||
|
widget:
|
||||||
|
type: homeassistant
|
||||||
|
url: http://14.4.4.20:8123
|
||||||
|
key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZWUyNTE1MDk0NGU0ZjRjYmJkMGM1MWI5MThmY2EzYiIsImlhdCI6MTc0NjgxMDM3NSwiZXhwIjoyMDYyMTcwMzc1fQ.Le6M7WL63nxtLTxccFacQR5GVUY5C-a_zGtmXT_uaz4
|
||||||
|
custom:
|
||||||
|
- template: "{{ states('sensor.st_00158555_temperature') | float(0) | round(1) }} °F"
|
||||||
|
label: Outdoor Temp
|
||||||
|
- template: "{{ states('sensor.st_00158555_feels_like') | float(0) | round(1) }} °F"
|
||||||
|
label: Feels Like
|
||||||
|
- template: "{{ states('sensor.st_00158555_humidity') | float(0) | round(1) }} %"
|
||||||
|
label: Humidity
|
||||||
|
- template: "{{ states('sensor.st_00158555_wind_speed') | float(0) | round(1) }} mph"
|
||||||
|
label: Wind Speed
|
||||||
|
"""
|
||||||
|
|
||||||
|
settings_yaml = """title: SnarfNet Operations & Telemetry
|
||||||
|
headerStyle: boxed
|
||||||
|
color: slate
|
||||||
|
theme: dark
|
||||||
|
statusStyle: badge
|
||||||
|
background:
|
||||||
|
image: https://i.imgur.com/hOzzkCY.jpeg
|
||||||
|
blur: sm
|
||||||
|
opacity: 50
|
||||||
|
brightness: 50
|
||||||
|
cardBlur: sm
|
||||||
|
quicklaunch:
|
||||||
|
searchDescriptions: true
|
||||||
|
hideInternetSearch: true
|
||||||
|
hideVisitURL: true
|
||||||
|
hideVersion: true
|
||||||
|
layout:
|
||||||
|
Critical Services:
|
||||||
|
icon: mdi-shield-check
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
GitOps & Automation:
|
||||||
|
icon: mdi-git
|
||||||
|
style: row
|
||||||
|
columns: 4
|
||||||
|
Storage & Clusters:
|
||||||
|
icon: mdi-server-network
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
Infrastructure & Observability:
|
||||||
|
icon: mdi-chart-timeline-variant
|
||||||
|
style: row
|
||||||
|
columns: 3
|
||||||
|
Environment & Weather:
|
||||||
|
icon: mdi-weather-partly-cloudy
|
||||||
|
style: row
|
||||||
|
columns: 1
|
||||||
|
showStats: true
|
||||||
|
providers:
|
||||||
|
longhorn:
|
||||||
|
url: http://longhorn-backend.longhorn-system.svc.cluster.local:9500
|
||||||
|
"""
|
||||||
|
|
||||||
|
widgets_yaml = """- kubernetes:
|
||||||
|
cluster:
|
||||||
|
show: true
|
||||||
|
cpu: true
|
||||||
|
memory: true
|
||||||
|
show_names: true
|
||||||
|
nodes:
|
||||||
|
show: false
|
||||||
|
- longhorn:
|
||||||
|
total: true
|
||||||
|
nodes: false
|
||||||
|
- resources:
|
||||||
|
label: System
|
||||||
|
backend: resources
|
||||||
|
cpu: true
|
||||||
|
expanded: true
|
||||||
|
memory: true
|
||||||
|
disk: /
|
||||||
|
- search:
|
||||||
|
provider: duckduckgo
|
||||||
|
target: _blank
|
||||||
|
"""
|
||||||
|
|
||||||
|
bookmarks_yaml = """- Quick Links:
|
||||||
|
- GitHub:
|
||||||
|
- abbr: GH
|
||||||
|
href: https://github.com/
|
||||||
|
- UniFi Site Manager:
|
||||||
|
- abbr: UI
|
||||||
|
href: https://unifi.ui.com/
|
||||||
|
- Cloudflare:
|
||||||
|
- abbr: CF
|
||||||
|
href: https://dash.cloudflare.com/
|
||||||
|
"""
|
||||||
|
|
||||||
|
cm['data']['services.yaml'] = services_yaml
|
||||||
|
cm['data']['settings.yaml'] = settings_yaml
|
||||||
|
cm['data']['widgets.yaml'] = widgets_yaml
|
||||||
|
cm['data']['bookmarks.yaml'] = bookmarks_yaml
|
||||||
|
cm['data']['kubernetes.yaml'] = "mode: cluster\n"
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'w', encoding='utf-8') as f:
|
||||||
|
yaml.dump_all(docs, f, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
||||||
|
|
||||||
|
print("Saved clean homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml successfully!")
|
||||||
33
scripts/apply_grafana_dashboards.py
Normal file
33
scripts/apply_grafana_dashboards.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
return out + ("\nERR: " + err if err else "")
|
||||||
|
|
||||||
|
with open("homelab-gitops-repo/manifests/aerodrome/grafana/grafana.yaml", "r", encoding="utf-8") as f:
|
||||||
|
grafana_yaml = f.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/grafana_updated.yaml")
|
||||||
|
stdin.write(grafana_yaml)
|
||||||
|
stdin.close()
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
|
||||||
|
print("Applying Grafana manifests:")
|
||||||
|
print(run("kubectl apply -f /tmp/grafana_updated.yaml"))
|
||||||
|
|
||||||
|
print("\nRestarting Grafana:")
|
||||||
|
print(run("kubectl rollout restart deployment/grafana -n default"))
|
||||||
|
print(run("kubectl rollout status deployment/grafana -n default --timeout=60s"))
|
||||||
|
|
||||||
|
print("\nWaiting for Grafana to finish initialization...")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
20
scripts/apply_grafana_server_side.py
Normal file
20
scripts/apply_grafana_server_side.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
return out + ("\nERR: " + err if err else "")
|
||||||
|
|
||||||
|
print("Applying server-side:")
|
||||||
|
print(run("kubectl apply -f /tmp/grafana_full.yaml --server-side --force-conflicts"))
|
||||||
|
print("\nRollout restart:")
|
||||||
|
print(run("kubectl rollout restart deployment/grafana -n default"))
|
||||||
|
print(run("kubectl rollout status deployment/grafana -n default --timeout=60s"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
149
scripts/apply_h21_authentik.py
Normal file
149
scripts/apply_h21_authentik.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
adv_config = """# Buffer tuning for Authentik headers
|
||||||
|
proxy_buffers 8 16k;
|
||||||
|
proxy_buffer_size 32k;
|
||||||
|
|
||||||
|
# Outpost verification route - plain HTTP to internal VIP
|
||||||
|
location /outpost.goauthentik.io {
|
||||||
|
auth_request off;
|
||||||
|
proxy_pass http://140.44.4.29:9000/outpost.goauthentik.io;
|
||||||
|
proxy_set_header Host authentik.snarfnet.net;
|
||||||
|
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect to Authentik when unauthenticated
|
||||||
|
location @goauthentik_proxy_signin {
|
||||||
|
internal;
|
||||||
|
auth_request off;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
return 302 https://authentik.snarfnet.net/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Protected Topology HTML (Interactive)
|
||||||
|
location = /topology.html {
|
||||||
|
auth_request /outpost.goauthentik.io/auth/nginx;
|
||||||
|
error_page 401 = @goauthentik_proxy_signin;
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
expires 0;
|
||||||
|
|
||||||
|
alias /data/topology/snarfnet_homelab_topology.html;
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Protected Topology PNG (High-Res Image)
|
||||||
|
location = /topology.png {
|
||||||
|
auth_request /outpost.goauthentik.io/auth/nginx;
|
||||||
|
error_page 401 = @goauthentik_proxy_signin;
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
expires 0;
|
||||||
|
|
||||||
|
alias /data/topology/snarfnet_homelab_topology.png;
|
||||||
|
default_type image/png;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /snarfnet_homelab_topology.html {
|
||||||
|
auth_request /outpost.goauthentik.io/auth/nginx;
|
||||||
|
error_page 401 = @goauthentik_proxy_signin;
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
expires 0;
|
||||||
|
|
||||||
|
alias /data/topology/snarfnet_homelab_topology.html;
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /snarfnet_homelab_topology.png {
|
||||||
|
auth_request /outpost.goauthentik.io/auth/nginx;
|
||||||
|
error_page 401 = @goauthentik_proxy_signin;
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
expires 0;
|
||||||
|
|
||||||
|
alias /data/topology/snarfnet_homelab_topology.png;
|
||||||
|
default_type image/png;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/adv_config_h21.txt")
|
||||||
|
stdin.write(adv_config)
|
||||||
|
stdin.close()
|
||||||
|
stdout.read()
|
||||||
|
|
||||||
|
remote_script = """import sqlite3, shutil, os
|
||||||
|
|
||||||
|
db_path = '/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite'
|
||||||
|
conf_path = '/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/21.conf'
|
||||||
|
|
||||||
|
with open('/tmp/adv_config_h21.txt', 'r') as f:
|
||||||
|
adv = f.read()
|
||||||
|
|
||||||
|
# Update DB
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("UPDATE proxy_host SET advanced_config = ? WHERE id = 21", (adv,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("Updated database for host 21.")
|
||||||
|
|
||||||
|
# Update 21.conf
|
||||||
|
with open(conf_path, 'r') as f:
|
||||||
|
conf = f.read()
|
||||||
|
|
||||||
|
# Strip existing adv_config block if present
|
||||||
|
tag = '# Buffer tuning for Authentik headers'
|
||||||
|
if tag in conf:
|
||||||
|
idx_start = conf.find(tag)
|
||||||
|
idx_end = conf.find('location / {', idx_start)
|
||||||
|
if idx_start != -1 and idx_end != -1:
|
||||||
|
new_conf = conf[:idx_start] + adv + "\\n\\n " + conf[idx_end:]
|
||||||
|
else:
|
||||||
|
new_conf = conf
|
||||||
|
else:
|
||||||
|
idx = conf.find('location / {')
|
||||||
|
if idx != -1:
|
||||||
|
new_conf = conf[:idx] + adv + "\\n\\n " + conf[idx:]
|
||||||
|
else:
|
||||||
|
new_conf = conf.rstrip().rstrip('}') + "\\n\\n" + adv + "\\n}\\n"
|
||||||
|
|
||||||
|
with open(conf_path, 'w') as f:
|
||||||
|
f.write(new_conf)
|
||||||
|
print("Updated 21.conf.")
|
||||||
|
"""
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/apply_h21.py")
|
||||||
|
stdin.write(remote_script)
|
||||||
|
stdin.close()
|
||||||
|
stdout.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("python3 /tmp/apply_h21.py")
|
||||||
|
print("Remote python stdout:", stdout.read().decode())
|
||||||
|
print("Remote python stderr:", stderr.read().decode())
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q --filter 'name=nginx-proxy-manager_app') nginx -t")
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
print("nginx -t stdout:", out)
|
||||||
|
print("nginx -t stderr:", err)
|
||||||
|
|
||||||
|
if "successful" in out or "successful" in err:
|
||||||
|
print("Nginx syntax OK! Reloading nginx...")
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q --filter 'name=nginx-proxy-manager_app') nginx -s reload")
|
||||||
|
print("Reload stdout:", stdout.read().decode())
|
||||||
|
print("Reload stderr:", stderr.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
31
scripts/apply_homepage.py
Normal file
31
scripts/apply_homepage.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
print(f"\n>>> {cmd}")
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
if out:
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("[stderr]", err)
|
||||||
|
return out
|
||||||
|
|
||||||
|
with open('homelab-gitops-repo/manifests/spaceport/homepage/homepage.yaml', 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command('cat > /tmp/homepage.yaml')
|
||||||
|
stdin.write(content)
|
||||||
|
stdin.close()
|
||||||
|
stdout.read()
|
||||||
|
|
||||||
|
run("ls -lh /tmp/homepage.yaml")
|
||||||
|
run("kubectl apply -f /tmp/homepage.yaml")
|
||||||
|
run("kubectl rollout restart deployment/homepage-2-1744068158 -n infrastructure")
|
||||||
|
run("kubectl rollout status deployment/homepage-2-1744068158 -n infrastructure --timeout=60s")
|
||||||
|
|
||||||
|
c.close()
|
||||||
140
scripts/apply_npm_fixes.py
Normal file
140
scripts/apply_npm_fixes.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
print("1. Creating backup of database and configs...")
|
||||||
|
backup_cmd = """
|
||||||
|
cp /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite.bak.$(date +%s)
|
||||||
|
"""
|
||||||
|
stdin, stdout, stderr = c.exec_command(backup_cmd)
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
print("Backup complete.")
|
||||||
|
|
||||||
|
print("2. Updating database.sqlite for hosts 45, 41, and 37...")
|
||||||
|
py_db = """
|
||||||
|
import sqlite3, datetime
|
||||||
|
conn = sqlite3.connect("/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite")
|
||||||
|
cur = conn.cursor()
|
||||||
|
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
updates = [
|
||||||
|
(45, "140.44.4.34", 443, "https"), # Longhorn -> Aerodrome Traefik
|
||||||
|
(41, "140.44.4.62", 443, "https"), # Semaphore -> Spaceport Traefik
|
||||||
|
(37, "140.44.4.34", 443, "https"), # Uptime Kuma -> Aerodrome Traefik
|
||||||
|
]
|
||||||
|
|
||||||
|
for pid, host, port, scheme in updates:
|
||||||
|
cur.execute('''UPDATE proxy_host
|
||||||
|
SET forward_host = ?, forward_port = ?, forward_scheme = ?,
|
||||||
|
allow_websocket_upgrade = 1, http2_support = 1, modified_on = ?
|
||||||
|
WHERE id = ?''', (host, port, scheme, now, pid))
|
||||||
|
print(f"DB updated: Host {pid} -> {scheme}://{host}:{port}")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
"""
|
||||||
|
stdin, stdout, stderr = c.exec_command(f"python3 -c '{py_db}'")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
err = stderr.read().decode()
|
||||||
|
if err: print("DB err:", err)
|
||||||
|
|
||||||
|
print("3. Generating updated nginx configuration files...")
|
||||||
|
|
||||||
|
def generate_conf(pid, domains, cert_id, host, port, scheme):
|
||||||
|
return f"""# ------------------------------------------------------------
|
||||||
|
# {domains}
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
map $scheme $hsts_header {{
|
||||||
|
https "max-age=63072000; preload";
|
||||||
|
}}
|
||||||
|
|
||||||
|
server {{
|
||||||
|
set $forward_scheme {scheme};
|
||||||
|
set $server "{host}";
|
||||||
|
set $port {port};
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
server_name {domains};
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
# Let's Encrypt SSL
|
||||||
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
|
include conf.d/include/ssl-cache.conf;
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/npm-{cert_id}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/npm-{cert_id}/privkey.pem;
|
||||||
|
|
||||||
|
# Asset Caching
|
||||||
|
include conf.d/include/assets.conf;
|
||||||
|
|
||||||
|
# Block Exploits
|
||||||
|
include conf.d/include/block-exploits.conf;
|
||||||
|
|
||||||
|
# Force SSL
|
||||||
|
set $trust_forwarded_proto "F";
|
||||||
|
include conf.d/include/force-ssl.conf;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $http_connection;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
access_log /data/logs/proxy-host-{pid}_access.log proxy;
|
||||||
|
error_log /data/logs/proxy-host-{pid}_error.log warn;
|
||||||
|
|
||||||
|
location / {{
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $http_connection;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# Proxy!
|
||||||
|
include conf.d/include/proxy.conf;
|
||||||
|
}}
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
include /data/nginx/custom/server_proxy[.]conf;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
configs = [
|
||||||
|
(45, "longhorn.aerodrome.snarfnet.net", 37, "140.44.4.34", 443, "https"),
|
||||||
|
(41, "semaphore.snarfnet.net", 33, "140.44.4.62", 443, "https"),
|
||||||
|
(37, "uptime.snarfnet.net", 27, "140.44.4.34", 443, "https"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for pid, dom, cert_id, host, port, scheme in configs:
|
||||||
|
cfg = generate_conf(pid, dom, cert_id, host, port, scheme)
|
||||||
|
path = f"/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/{pid}.conf"
|
||||||
|
cmd = f"cat << 'EOF' > {path}\n{cfg}\nEOF\n"
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
print(f"Wrote config: {path}")
|
||||||
|
|
||||||
|
print("4. Testing Nginx config inside container...")
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) nginx -t")
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
print("nginx -t stdout:", out)
|
||||||
|
print("nginx -t stderr:", err)
|
||||||
|
|
||||||
|
if "successful" in out or "successful" in err:
|
||||||
|
print("5. Reloading Nginx via Swarm service update...")
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker service update --force nginx-proxy-manager_app")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
print("Reload completed.")
|
||||||
|
else:
|
||||||
|
print("ERROR: Nginx config test failed!")
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
26
scripts/apply_pihole_redirect.py
Normal file
26
scripts/apply_pihole_redirect.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
def apply_manifest(host, cluster_name, file_path):
|
||||||
|
print(f"Applying {file_path} to {cluster_name} ({host})...")
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
manifest_content = f.read()
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(host, username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl apply -f -')
|
||||||
|
stdin.write(manifest_content)
|
||||||
|
stdin.channel.shutdown_write()
|
||||||
|
|
||||||
|
out = stdout.read().decode('utf-8')
|
||||||
|
err = stderr.read().decode('utf-8')
|
||||||
|
print("Output:")
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("Stderr:", err)
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
apply_manifest('140.44.4.11', 'Aerodrome', r'homelab-gitops-repo/manifests/aerodrome/dns/dns-stack.yaml')
|
||||||
|
apply_manifest('140.44.4.41', 'Spaceport', r'homelab-gitops-repo/manifests/spaceport/dns/dns-stack.yaml')
|
||||||
|
|
||||||
45
scripts/apply_prom_grafana.py
Normal file
45
scripts/apply_prom_grafana.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
return out + ("\nERR: " + err if err else "")
|
||||||
|
|
||||||
|
with open("homelab-gitops-repo/manifests/aerodrome/prometheus/prometheus.yaml", "r") as f:
|
||||||
|
prom_yaml = f.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/prometheus.yaml")
|
||||||
|
stdin.write(prom_yaml)
|
||||||
|
stdin.close()
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
|
||||||
|
print("Applying Prometheus:")
|
||||||
|
print(run("kubectl apply -f /tmp/prometheus.yaml"))
|
||||||
|
|
||||||
|
with open("homelab-gitops-repo/manifests/aerodrome/grafana/grafana.yaml", "r") as f:
|
||||||
|
grafana_yaml = f.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/grafana_full.yaml")
|
||||||
|
stdin.write(grafana_yaml)
|
||||||
|
stdin.close()
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
|
||||||
|
print("\nApplying Grafana configuration & dashboards:")
|
||||||
|
print(run("kubectl apply -f /tmp/grafana_full.yaml"))
|
||||||
|
|
||||||
|
print("\nRestarting Grafana:")
|
||||||
|
print(run("kubectl rollout restart deployment/grafana -n default"))
|
||||||
|
print(run("kubectl rollout status deployment/grafana -n default --timeout=60s"))
|
||||||
|
|
||||||
|
print("\nChecking Prometheus pod:")
|
||||||
|
time.sleep(5)
|
||||||
|
print(run("kubectl get pods -l app.kubernetes.io/name=prometheus"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
36
scripts/apply_prom_update.py
Normal file
36
scripts/apply_prom_update.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
return out + ("\nERR: " + err if err else "")
|
||||||
|
|
||||||
|
with open("homelab-gitops-repo/manifests/aerodrome/prometheus/prometheus.yaml", "r", encoding="utf-8") as f:
|
||||||
|
prom_yaml = f.read()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/prometheus_updated.yaml")
|
||||||
|
stdin.write(prom_yaml)
|
||||||
|
stdin.close()
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
|
||||||
|
print("Applying Prometheus:")
|
||||||
|
print(run("kubectl apply -f /tmp/prometheus_updated.yaml"))
|
||||||
|
|
||||||
|
print("\nRestarting Prometheus:")
|
||||||
|
print(run("kubectl rollout restart deployment/prometheus -n default"))
|
||||||
|
print(run("kubectl rollout status deployment/prometheus -n default --timeout=60s"))
|
||||||
|
|
||||||
|
print("\nWaiting 15 seconds for scrape loop...")
|
||||||
|
time.sleep(15)
|
||||||
|
|
||||||
|
print("\nPrometheus active targets count:")
|
||||||
|
print(run("kubectl exec deployment/prometheus -- wget -qO- http://localhost:9090/api/v1/targets | grep -o '\"health\":\"up\"' | wc -l"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
72
scripts/apply_semaphore_fix.py
Normal file
72
scripts/apply_semaphore_fix.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import paramiko
|
||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
print("1. Updating Authentik Provider 10 redirect_uris...")
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider_update = {
|
||||||
|
"redirect_uris": [
|
||||||
|
{"matching_mode": "strict", "url": "https://semaphore.snarfnet.net/auth/oidc/authentik/redirect"},
|
||||||
|
{"matching_mode": "strict", "url": "https://semaphore.snarfnet.net/api/auth/oidc/authentik/redirect/"},
|
||||||
|
{"matching_mode": "regex", "url": "https://semaphore\\.snarfnet\\.net/.*"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"https://authentik.snarfnet.net/api/v3/providers/oauth2/10/",
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(provider_update).encode('utf-8'),
|
||||||
|
method="PATCH"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, context=ctx, timeout=8) as resp:
|
||||||
|
res = json.loads(resp.read().decode())
|
||||||
|
print(" ✓ Authentik Provider 10 updated successfully:", res.get('redirect_uris'))
|
||||||
|
except Exception as e:
|
||||||
|
print(" ✗ Failed to update Authentik Provider 10:", e)
|
||||||
|
|
||||||
|
print("\n2. Applying updated Semaphore manifest to Spaceport (140.44.4.41)...")
|
||||||
|
manifest_path = r"C:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\homelab-gitops-repo\manifests\spaceport\semaphore\semaphore.yaml"
|
||||||
|
with open(manifest_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl apply -f -")
|
||||||
|
stdin.write(content)
|
||||||
|
stdin.channel.shutdown_write()
|
||||||
|
print("Apply output:", stdout.read().decode())
|
||||||
|
err = stderr.read().decode()
|
||||||
|
if err: print("Apply stderr:", err)
|
||||||
|
|
||||||
|
print("3. Rolling out Semaphore deployment...")
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl rollout restart deployment/semaphore -n infrastructure; k3s kubectl rollout status deployment/semaphore -n infrastructure --timeout=90s")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
# Check new pod env
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl get pods -n infrastructure -l app.kubernetes.io/name=semaphore -o jsonpath='{.items[0].metadata.name}'")
|
||||||
|
new_pod = stdout.read().decode().strip()
|
||||||
|
print(f"New pod: {new_pod}")
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(f"k3s kubectl exec -n infrastructure {new_pod} -- env | grep SEMAPHORE_OIDC_PROVIDERS")
|
||||||
|
print("New env:", stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
print("Done!")
|
||||||
|
|
||||||
161
scripts/apply_synology_fixes.py
Normal file
161
scripts/apply_synology_fixes.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
print("1. Updating SQLite database for hosts 11 and 28...")
|
||||||
|
dsm_adv = """proxy_buffer_size 128k;
|
||||||
|
proxy_buffers 8 256k;
|
||||||
|
proxy_busy_buffers_size 256k;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
proxy_ssl_verify off;"""
|
||||||
|
|
||||||
|
py_db = f"""
|
||||||
|
import sqlite3, datetime
|
||||||
|
conn = sqlite3.connect('/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite')
|
||||||
|
cur = conn.cursor()
|
||||||
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
adv = '''{dsm_adv}'''
|
||||||
|
|
||||||
|
# Host 11: sn-nas.snarfnet.net -> 14.10.10.71:5001
|
||||||
|
cur.execute('''UPDATE proxy_host
|
||||||
|
SET forward_host = "14.10.10.71", forward_port = 5001, forward_scheme = "https",
|
||||||
|
allow_websocket_upgrade = 1, http2_support = 1, advanced_config = ?, modified_on = ?
|
||||||
|
WHERE id = 11''', (adv, now))
|
||||||
|
|
||||||
|
# Host 28: snarfnas.snarfnet.net -> 14.10.10.72:5001 (Clean DSM, remove dead outpost)
|
||||||
|
cur.execute('''UPDATE proxy_host
|
||||||
|
SET forward_host = "14.10.10.72", forward_port = 5001, forward_scheme = "https",
|
||||||
|
allow_websocket_upgrade = 1, http2_support = 1, advanced_config = ?, modified_on = ?
|
||||||
|
WHERE id = 28''', (adv, now))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
print("Database updated for hosts 11 and 28.")
|
||||||
|
conn.close()
|
||||||
|
"""
|
||||||
|
|
||||||
|
sftp = c.open_sftp()
|
||||||
|
with sftp.open('/tmp/update_nas_db.py', 'w') as f:
|
||||||
|
f.write(py_db)
|
||||||
|
sftp.close()
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("python3 /tmp/update_nas_db.py")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
err = stderr.read().decode()
|
||||||
|
if err: print("DB err:", err)
|
||||||
|
|
||||||
|
print("2. Generating Nginx configuration files for hosts 11 and 28...")
|
||||||
|
|
||||||
|
def make_dsm_conf(pid, domain, cert_id, host, port):
|
||||||
|
return f"""# ------------------------------------------------------------
|
||||||
|
# {domain}
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
map $scheme $hsts_header {{
|
||||||
|
https "max-age=63072000;includeSubDomains; preload";
|
||||||
|
}}
|
||||||
|
|
||||||
|
server {{
|
||||||
|
set $forward_scheme https;
|
||||||
|
set $server "{host}";
|
||||||
|
set $port {port};
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
server_name {domain};
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
# Let's Encrypt SSL
|
||||||
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
|
include conf.d/include/ssl-cache.conf;
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/npm-{cert_id}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/npm-{cert_id}/privkey.pem;
|
||||||
|
|
||||||
|
# Asset Caching
|
||||||
|
include conf.d/include/assets.conf;
|
||||||
|
|
||||||
|
# Block Exploits
|
||||||
|
include conf.d/include/block-exploits.conf;
|
||||||
|
|
||||||
|
# HSTS
|
||||||
|
add_header Strict-Transport-Security $hsts_header always;
|
||||||
|
|
||||||
|
# Force SSL
|
||||||
|
set $trust_forwarded_proto "F";
|
||||||
|
include conf.d/include/force-ssl.conf;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $http_connection;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
access_log /data/logs/proxy-host-{pid}_access.log proxy;
|
||||||
|
error_log /data/logs/proxy-host-{pid}_error.log warn;
|
||||||
|
|
||||||
|
location / {{
|
||||||
|
# HSTS
|
||||||
|
add_header Strict-Transport-Security $hsts_header always;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $http_connection;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# DSM Buffer Tuning
|
||||||
|
proxy_buffer_size 128k;
|
||||||
|
proxy_buffers 8 256k;
|
||||||
|
proxy_busy_buffers_size 256k;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
|
||||||
|
# Proxy!
|
||||||
|
include conf.d/include/proxy.conf;
|
||||||
|
}}
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
include /data/nginx/custom/server_proxy[.]conf;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
conf11 = make_dsm_conf(11, "sn-nas.snarfnet.net", 12, "14.10.10.71", 5001)
|
||||||
|
conf28 = make_dsm_conf(28, "snarfnas.snarfnet.net", 22, "14.10.10.72", 5001)
|
||||||
|
|
||||||
|
path11 = "/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/11.conf"
|
||||||
|
path28 = "/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/28.conf"
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command(f"cat << 'EOF' > {path11}\n{conf11}\nEOF\n")
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
print(f"Wrote {path11}")
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command(f"cat << 'EOF' > {path28}\n{conf28}\nEOF\n")
|
||||||
|
stdout.channel.recv_exit_status()
|
||||||
|
print(f"Wrote {path28}")
|
||||||
|
|
||||||
|
print("3. Testing Nginx configuration...")
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) nginx -t")
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
print("nginx -t stdout:", out)
|
||||||
|
print("nginx -t stderr:", err)
|
||||||
|
|
||||||
|
if "successful" in out or "successful" in err:
|
||||||
|
print("4. Reloading Nginx Swarm service...")
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker service update --force nginx-proxy-manager_app")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
print("Reload complete!")
|
||||||
|
else:
|
||||||
|
print("ERROR: Nginx config invalid!")
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
75
scripts/audit_harvester_hardware.py
Normal file
75
scripts/audit_harvester_hardware.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
"Node 1 (snaihvs0lnx01)": "140.44.44.121",
|
||||||
|
"Node 2 (snaihvs0lnx02)": "140.44.44.122",
|
||||||
|
"Node 3 (snaihvs0lnx03)": "140.44.44.123",
|
||||||
|
"Node 4 (idrac-spacedock4)": "140.44.44.124",
|
||||||
|
}
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for name, ip in nodes.items():
|
||||||
|
print(f"Connecting to {name} ({ip})...")
|
||||||
|
node_data = {}
|
||||||
|
try:
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect(ip, username="root", password="Katzir476!", timeout=10)
|
||||||
|
chan = c.invoke_shell()
|
||||||
|
time.sleep(2)
|
||||||
|
if chan.recv_ready():
|
||||||
|
chan.recv(4096)
|
||||||
|
|
||||||
|
def send_and_recv(cmd, wait_time=2.5):
|
||||||
|
chan.send(cmd + "\r\n")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
out = ""
|
||||||
|
while chan.recv_ready():
|
||||||
|
out += chan.recv(8192).decode('utf-8', errors='ignore')
|
||||||
|
time.sleep(0.5)
|
||||||
|
return out
|
||||||
|
|
||||||
|
# 1. BIOS Settings
|
||||||
|
raw_bios = send_and_recv("racadm get bios.SysProfileSettings", 2)
|
||||||
|
for key in ["SysProfile", "ProcCStates", "ProcC1E", "ProcTurboMode", "ProcPwrPerf"]:
|
||||||
|
m = re.search(rf"{key}\s*=\s*(\w+)", raw_bios)
|
||||||
|
node_data[key] = m.group(1) if m else "Unknown"
|
||||||
|
|
||||||
|
raw_proc = send_and_recv("racadm get bios.ProcSettings", 2)
|
||||||
|
for key in ["ProcVirtualization", "ProcExecuteDisable", "LogicalProc"]:
|
||||||
|
m = re.search(rf"{key}\s*=\s*(\w+)", raw_proc)
|
||||||
|
node_data[key] = m.group(1) if m else "Unknown"
|
||||||
|
|
||||||
|
raw_dev = send_and_recv("racadm get bios.IntegratedDevices", 2)
|
||||||
|
for key in ["SriovGlobalEnable"]:
|
||||||
|
m = re.search(rf"{key}\s*=\s*(\w+)", raw_dev)
|
||||||
|
node_data[key] = m.group(1) if m else "Unknown"
|
||||||
|
|
||||||
|
# 2. Storage / RAID
|
||||||
|
raw_ctrl = send_and_recv("racadm raid get controllers", 2)
|
||||||
|
controllers = [line.strip() for line in raw_ctrl.splitlines() if "RAID." in line or "PERC" in line or "AHCI." in line]
|
||||||
|
node_data["Controllers"] = ", ".join(controllers) if controllers else "None"
|
||||||
|
|
||||||
|
raw_vdisks = send_and_recv("racadm raid get vdisks", 3)
|
||||||
|
vdisks = [line.strip() for line in raw_vdisks.splitlines() if line.startswith("Disk.Virtual")]
|
||||||
|
node_data["VirtualDisks"] = len(vdisks)
|
||||||
|
node_data["VirtualDisksList"] = vdisks
|
||||||
|
|
||||||
|
raw_pdisks = send_and_recv("racadm raid get pdisks", 4)
|
||||||
|
pdisk_lines = [line.strip() for line in raw_pdisks.splitlines() if line.startswith("Disk.Bay")]
|
||||||
|
node_data["PhysicalDisks"] = len(pdisk_lines)
|
||||||
|
node_data["PDisksSummary"] = raw_pdisks
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
except Exception as e:
|
||||||
|
node_data["Error"] = str(e)
|
||||||
|
|
||||||
|
results[name] = node_data
|
||||||
|
|
||||||
|
print("\n\n========================= SUMMARY MATRIX =========================")
|
||||||
|
import json
|
||||||
|
print(json.dumps(results, indent=2))
|
||||||
|
|
||||||
24
scripts/audit_harvester_live_nodes.py
Normal file
24
scripts/audit_harvester_live_nodes.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.90', username='rancher', password='Katzir476!', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(f"echo Katzir476! | sudo -S sh -c '{cmd}'")
|
||||||
|
return stdout.read().decode().strip()
|
||||||
|
|
||||||
|
print("=== CPU & VIRTUALIZATION (lscpu) ===")
|
||||||
|
print(run("lscpu | grep -E 'Model name|CPU\(s\):|Thread|Socket|Virtualization|NUMA'"))
|
||||||
|
|
||||||
|
print("\n=== KERNEL PARAMETERS (/proc/cmdline) ===")
|
||||||
|
print(run("cat /proc/cmdline"))
|
||||||
|
|
||||||
|
print("\n=== STORAGE CONTROLLER (lspci) ===")
|
||||||
|
print(run("lspci | grep -E 'RAID|SAS|SATA|Ethernet|Network'"))
|
||||||
|
|
||||||
|
print("\n=== BLOCK DEVICES (lsblk) ===")
|
||||||
|
print(run("lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
48
scripts/audit_zabbix_alerts.py
Normal file
48
scripts/audit_zabbix_alerts.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import json, ssl, urllib.request
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
ZABBIX_URL = "http://140.44.4.60:8888/api_jsonrpc.php"
|
||||||
|
|
||||||
|
def zbx_req(method, params, token=None):
|
||||||
|
payload = {'jsonrpc': '2.0', 'method': method, 'params': params, 'id': 1}
|
||||||
|
headers = {'Content-Type': 'application/json-rpc'}
|
||||||
|
if token:
|
||||||
|
headers['Authorization'] = f'Bearer {token}'
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST')
|
||||||
|
with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
|
||||||
|
return json.loads(r.read().decode())['result']
|
||||||
|
|
||||||
|
token = zbx_req('user.login', {'username': 'Admin', 'password': 'zabbix'})
|
||||||
|
print("Zabbix Auth Token:", token)
|
||||||
|
|
||||||
|
# 1. Actions
|
||||||
|
actions = zbx_req('action.get', {'output': 'extend', 'selectOperations': 'extend', 'selectFilter': 'extend'}, token)
|
||||||
|
print("\n=== TRIGGER ACTIONS ===")
|
||||||
|
for a in actions:
|
||||||
|
print(f"ID: {a['actionid']}, Name: {a['name']}, Status (0=enabled, 1=disabled): {a['status']}, EventSource: {a['eventsource']}")
|
||||||
|
print(f" Operations: {a.get('operations')}")
|
||||||
|
print(f" Filter: {a.get('filter')}")
|
||||||
|
|
||||||
|
# 2. Media Types
|
||||||
|
media_types = zbx_req('mediatype.get', {'output': 'extend'}, token)
|
||||||
|
print("\n=== MEDIA TYPES ===")
|
||||||
|
for m in media_types:
|
||||||
|
print(f"ID: {m['mediatypeid']}, Name: {m['name']}, Type: {m['type']}, Status: {m['status']}")
|
||||||
|
|
||||||
|
# 3. User Media
|
||||||
|
users = zbx_req('user.get', {'output': ['userid', 'username'], 'selectMedias': 'extend'}, token)
|
||||||
|
print("\n=== USERS & MEDIA ===")
|
||||||
|
for u in users:
|
||||||
|
print(f"User: {u['username']} (ID: {u['userid']})")
|
||||||
|
for med in u.get('medias', []):
|
||||||
|
print(f" Media: mediatypeid={med['mediatypeid']}, sendto={med['sendto']}, active={med['active']}, severity={med['severity']}")
|
||||||
|
|
||||||
|
# 4. Recent Alerts
|
||||||
|
alerts = zbx_req('alert.get', {'output': 'extend', 'limit': 15, 'sortfield': 'clock', 'sortorder': 'DESC'}, token)
|
||||||
|
print("\n=== RECENT ALERTS ===")
|
||||||
|
for al in alerts:
|
||||||
|
print(f"Alert ID: {al['alertid']}, ActionID: {al['actionid']}, Clock: {al['clock']}, Error: {al.get('error')}, Status: {al.get('status')}, Subject: {al.get('subject')}")
|
||||||
|
|
||||||
27
scripts/capture_screenshot.py
Normal file
27
scripts/capture_screenshot.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
edge_path = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
|
||||||
|
out_png = os.path.abspath("docs/snarfnet_homelab_topology.png")
|
||||||
|
html_url = "file:///" + os.path.abspath("docs/snarfnet_homelab_topology.html").replace("\\", "/")
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
edge_path,
|
||||||
|
"--headless",
|
||||||
|
"--disable-gpu",
|
||||||
|
"--no-sandbox",
|
||||||
|
"--hide-scrollbars",
|
||||||
|
"--window-size=1920,2400",
|
||||||
|
f"--screenshot={out_png}",
|
||||||
|
html_url
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Running cmd:", " ".join(cmd))
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||||
|
print("Return code:", res.returncode)
|
||||||
|
print("Stdout:", res.stdout)
|
||||||
|
print("Stderr:", res.stderr)
|
||||||
|
print("File exists?", os.path.exists(out_png))
|
||||||
|
if os.path.exists(out_png):
|
||||||
|
print("File size:", os.path.getsize(out_png))
|
||||||
|
|
||||||
10
scripts/cat_21_conf.py
Normal file
10
scripts/cat_21_conf.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
cmd = "docker exec $(docker ps -q --filter name=nginx-proxy-manager_app) cat /data/nginx/proxy_host/21.conf"
|
||||||
|
_, o, _ = c.exec_command(cmd)
|
||||||
|
print(o.read().decode())
|
||||||
|
c.close()
|
||||||
|
|
||||||
13
scripts/cat_prom_conf.py
Normal file
13
scripts/cat_prom_conf.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print(run("kubectl exec deployment/prometheus -- cat /etc/prometheus/prometheus.yml"))
|
||||||
|
c.close()
|
||||||
|
|
||||||
12
scripts/check_33_conf.py
Normal file
12
scripts/check_33_conf.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.71", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'cat /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/33.conf'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("33.conf:\n", stdout.read().decode())
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
13
scripts/check_43_access.py
Normal file
13
scripts/check_43_access.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.71', username='snarf', key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("sudo docker exec $(sudo docker ps -q -f name=nginx-proxy-manager) tail -n 30 /data/logs/proxy-host-43_access.log")
|
||||||
|
print("=== Access log 43 (pihole2) around 11:00 ===")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
12
scripts/check_49_conf.py
Normal file
12
scripts/check_49_conf.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.71", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'cat /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/49.conf'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("49.conf (ai.snarfnet.net):\n", stdout.read().decode())
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
17
scripts/check_55_and_43.py
Normal file
17
scripts/check_55_and_43.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.71', username='snarf', key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("sudo docker exec $(sudo docker ps -q -f name=nginx-proxy-manager) cat /data/nginx/proxy_host/55.conf")
|
||||||
|
print("=== 55.conf (pihole1) ===")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("sudo docker exec $(sudo docker ps -q -f name=nginx-proxy-manager) cat /data/nginx/proxy_host/43.conf")
|
||||||
|
print("=== 43.conf (pihole2) ===")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
24
scripts/check_aero_metrics_sources.py
Normal file
24
scripts/check_aero_metrics_sources.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print("Nodes on Aerodrome:")
|
||||||
|
print(run("kubectl get nodes -o wide"))
|
||||||
|
|
||||||
|
print("\nDaemonSets on Aerodrome:")
|
||||||
|
print(run("kubectl get ds -A"))
|
||||||
|
|
||||||
|
print("\nServices matching metrics:")
|
||||||
|
print(run("kubectl get svc -A | grep -E 'metric|node|cadvisor|exporter'"))
|
||||||
|
|
||||||
|
print("\nKubelet metrics on node 140.44.4.11 (port 10250 or 10255):")
|
||||||
|
print(run("curl -sk https://localhost:10250/metrics/cadvisor | head -n 10"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
12
scripts/check_aero_pihole_auth.py
Normal file
12
scripts/check_aero_pihole_auth.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s -X POST "http://localhost/api/auth" -H "Content-Type: application/json" -d \'{"password":"snarfnetadmin"}\'')
|
||||||
|
print("Aerodrome API auth test:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
19
scripts/check_alerts_status.py
Normal file
19
scripts/check_alerts_status.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import json, ssl, urllib.request
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
ZABBIX_URL = 'http://140.44.4.60:8888/api_jsonrpc.php'
|
||||||
|
|
||||||
|
payload = {'jsonrpc': '2.0', 'method': 'user.login', 'params': {'username': 'Admin', 'password': 'zabbix'}, 'id': 1}
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json-rpc'})
|
||||||
|
token = json.loads(urllib.request.urlopen(req, context=ctx).read())['result']
|
||||||
|
|
||||||
|
payload = {'jsonrpc': '2.0', 'method': 'alert.get', 'params': {'output': 'extend', 'limit': 20, 'sortfield': 'clock', 'sortorder': 'DESC'}, 'id': 2}
|
||||||
|
req = urllib.request.Request(ZABBIX_URL, data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json-rpc', 'Authorization': f'Bearer {token}'})
|
||||||
|
alerts = json.loads(urllib.request.urlopen(req, context=ctx).read())['result']
|
||||||
|
|
||||||
|
for a in alerts:
|
||||||
|
print(f"ID: {a['alertid']} | Status: {a['status']} | User: {a['userid']} | Error: '{a.get('error')}' | Subject: {a.get('subject')}")
|
||||||
|
|
||||||
25
scripts/check_all_npm_hosts.py
Normal file
25
scripts/check_all_npm_hosts.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
py_query = """
|
||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect('/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite')
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute('SELECT id, domain_names, forward_host, forward_port, forward_scheme, enabled FROM proxy_host')
|
||||||
|
for r in cur.fetchall():
|
||||||
|
print(r)
|
||||||
|
conn.close()
|
||||||
|
"""
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_query}"')
|
||||||
|
print("All NPM hosts:\n", stdout.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
30
scripts/check_all_providers.py
Normal file
30
scripts/check_all_providers.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
def api_call(url):
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
with urllib.request.urlopen(req, context=ctx, timeout=8) as resp:
|
||||||
|
return json.loads(resp.read().decode('utf-8'))
|
||||||
|
|
||||||
|
apps = api_call("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100")
|
||||||
|
for a in apps.get('results', []):
|
||||||
|
print(f"App: {a['name']:30} | Slug: {a['slug']:15} | Provider: {a.get('provider')}")
|
||||||
|
|
||||||
|
providers = api_call("https://authentik.snarfnet.net/api/v3/providers/proxy/?page_size=100")
|
||||||
|
for p in providers.get('results', []):
|
||||||
|
print(f"Proxy Provider: {p['name']:30} | PK: {p['pk']:4} | Mode: {p['mode']:15} | Cookie Domain: {p.get('cookie_domain')}")
|
||||||
|
|
||||||
12
scripts/check_api_status.py
Normal file
12
scripts/check_api_status.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/status"')
|
||||||
|
print("api/status response:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
12
scripts/check_apiurl.py
Normal file
12
scripts/check_apiurl.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s "http://localhost/admin/login" | grep -i "data-apiurl"')
|
||||||
|
print("data-apiurl:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
17
scripts/check_app_dll.py
Normal file
17
scripts/check_app_dll.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /app')
|
||||||
|
print("/app contents:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
# Search for AddAuthentication in dlls
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- strings /app/DnsServerApp.dll /app/DnsServerCore.dll 2>/dev/null | grep -i -C 3 "AddAuthentication"')
|
||||||
|
print("AddAuthentication strings:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
16
scripts/check_argo_secret.py
Normal file
16
scripts/check_argo_secret.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import paramiko
|
||||||
|
import base64
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print("Argo initial secret:")
|
||||||
|
print(run("kubectl get secret argocd-initial-admin-secret -n infrastructure -o jsonpath='{.data.password}'"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
73
scripts/check_argocd_apps.py
Normal file
73
scripts/check_argocd_apps.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJob21lcGFnZTphcGlLZXkiLCJuYmYiOjE3OTQ1MDIwNzgsImlhdCI6MTc5NDUwMjA3OCwianRpIjoiZjQ2NzU0NjMtODRhOS00MjI0LWI3ODItNmY5ODQxYWEwY2UxIn0.aHhrny2RBG-vAJAyXMvan0jw65jkP3bguqEduaG78O0"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.get("https://argo.snarfnet.net/api/v1/applications", headers=headers, verify=False, timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
items = data.get("items", [])
|
||||||
|
print(f"Total Applications: {len(items)}\n")
|
||||||
|
|
||||||
|
out_of_sync = []
|
||||||
|
synced = []
|
||||||
|
|
||||||
|
for app in items:
|
||||||
|
name = app.get("metadata", {}).get("name")
|
||||||
|
sync_status = app.get("status", {}).get("sync", {}).get("status")
|
||||||
|
health_status = app.get("status", {}).get("health", {}).get("status")
|
||||||
|
dest_server = app.get("spec", {}).get("destination", {}).get("server")
|
||||||
|
dest_ns = app.get("spec", {}).get("destination", {}).get("namespace")
|
||||||
|
repo = app.get("spec", {}).get("source", {}).get("repoURL")
|
||||||
|
path = app.get("spec", {}).get("source", {}).get("path")
|
||||||
|
|
||||||
|
# Check operation or sync conditions/messages
|
||||||
|
operation_state = app.get("status", {}).get("operationState", {})
|
||||||
|
op_phase = operation_state.get("phase")
|
||||||
|
op_msg = operation_state.get("message")
|
||||||
|
conditions = app.get("status", {}).get("conditions", [])
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"name": name,
|
||||||
|
"sync": sync_status,
|
||||||
|
"health": health_status,
|
||||||
|
"dest_ns": dest_ns,
|
||||||
|
"dest_server": dest_server,
|
||||||
|
"repo": repo,
|
||||||
|
"path": path,
|
||||||
|
"op_phase": op_phase,
|
||||||
|
"op_msg": op_msg,
|
||||||
|
"conditions": conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
if sync_status != "Synced" or health_status not in ["Healthy", None]:
|
||||||
|
out_of_sync.append(info)
|
||||||
|
else:
|
||||||
|
synced.append(info)
|
||||||
|
|
||||||
|
print(f"=== OUT OF SYNC / DEGRADED APPS ({len(out_of_sync)}) ===")
|
||||||
|
for a in out_of_sync:
|
||||||
|
print(f"\nApp: {a['name']}")
|
||||||
|
print(f" Sync: {a['sync']} | Health: {a['health']}")
|
||||||
|
print(f" Dest: {a['dest_ns']} on {a['dest_server']}")
|
||||||
|
print(f" Path: {a['path']}")
|
||||||
|
if a['op_phase']:
|
||||||
|
print(f" Op Phase: {a['op_phase']}")
|
||||||
|
if a['op_msg']:
|
||||||
|
print(f" Op Msg: {a['op_msg']}")
|
||||||
|
if a['conditions']:
|
||||||
|
print(f" Conditions: {a['conditions']}")
|
||||||
|
|
||||||
|
print(f"\n=== SYNCED APPS ({len(synced)}) ===")
|
||||||
|
for a in synced:
|
||||||
|
print(f" {a['name']}: Sync={a['sync']}, Health={a['health']}, NS={a['dest_ns']}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Error querying Argo CD API:", e)
|
||||||
|
|
||||||
29
scripts/check_argocd_resources.py
Normal file
29
scripts/check_argocd_resources.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJob21lcGFnZTphcGlLZXkiLCJuYmYiOjE3OTQ1MDIwNzgsImlhdCI6MTc5NDUwMjA3OCwianRpIjoiZjQ2NzU0NjMtODRhOS00MjI0LWI3ODItNmY5ODQxYWEwY2UxIn0.aHhrny2RBG-vAJAyXMvan0jw65jkP3bguqEduaG78O0"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = requests.get("https://argo.snarfnet.net/api/v1/applications", headers=headers, verify=False, timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
items = data.get("items", [])
|
||||||
|
|
||||||
|
for app in items:
|
||||||
|
name = app.get("metadata", {}).get("name")
|
||||||
|
sync = app.get("status", {}).get("sync", {}).get("status")
|
||||||
|
health = app.get("status", {}).get("health", {}).get("status")
|
||||||
|
resources = app.get("status", {}).get("resources", [])
|
||||||
|
|
||||||
|
out_resources = [r for r in resources if r.get("status") != "Synced" or r.get("health", {}).get("status") not in ["Healthy", None]]
|
||||||
|
|
||||||
|
print(f"[{name}] Sync: {sync}, Health: {health}, Total Resources: {len(resources)}")
|
||||||
|
if out_resources:
|
||||||
|
print(f" -> Out-of-sync / unhealthy resources:")
|
||||||
|
for r in out_resources:
|
||||||
|
print(f" {r.get('kind')}/{r.get('name')}: Sync={r.get('status')}, Health={r.get('health', {}).get('status')}")
|
||||||
|
|
||||||
@@ -23,3 +23,4 @@ print("Proxy Providers:")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ print("OAuth2/OIDC Providers:")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ print("Authentik OAuth2/OIDC Providers:")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
31
scripts/check_authentik_apps_full.py
Normal file
31
scripts/check_authentik_apps_full.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100", headers=headers)
|
||||||
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
||||||
|
data = json.loads(resp.read().decode())
|
||||||
|
print(f"Total Applications: {len(data['results'])}")
|
||||||
|
for app in sorted(data['results'], key=lambda x: x['name']):
|
||||||
|
slug = app['slug']
|
||||||
|
if any(k in slug for k in ['dns', 'pihole', 'technitium', 'syno', 'nas', 'semaphore']):
|
||||||
|
print(f"- Name: {app['name']}")
|
||||||
|
print(f" Slug: {app['slug']}")
|
||||||
|
print(f" Group: {app.get('group')}")
|
||||||
|
print(f" Launch URL: {app.get('meta_launch_url')}")
|
||||||
|
print(f" Icon: {app.get('meta_icon')}")
|
||||||
|
print()
|
||||||
|
|
||||||
36
scripts/check_authentik_events.py
Normal file
36
scripts/check_authentik_events.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
AK_BASE = "https://authentik.snarfnet.net"
|
||||||
|
AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {AK_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch recent events
|
||||||
|
req = urllib.request.Request(f"{AK_BASE}/api/v3/events/events/?page_size=20", headers=headers)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
|
||||||
|
data = json.loads(r.read().decode())
|
||||||
|
for ev in data.get("results", []):
|
||||||
|
action = ev.get("action")
|
||||||
|
user = ev.get("user", {}).get("username")
|
||||||
|
context = ev.get("context", {})
|
||||||
|
created = ev.get("created")
|
||||||
|
print(f"[{created}] Action: {action} | User: {user}")
|
||||||
|
if "message" in context or "redirect_uri" in str(context) or "error" in str(context) or "client_id" in str(context):
|
||||||
|
print(f" Context: {json.dumps(context, indent=2)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error querying Authentik: {e}")
|
||||||
|
|
||||||
@@ -17,3 +17,4 @@ print("=== ALL INGRESSES ===")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
19
scripts/check_authentik_logs.py
Normal file
19
scripts/check_authentik_logs.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
# Check Authentik server logs
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl logs -n infra deployment/authentik-server --tail=50")
|
||||||
|
print("Authentik server logs:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
# Check Authentik outpost logs if any
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl get pods -n infra")
|
||||||
|
print("Infra pods on Aerodrome:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
20
scripts/check_authentik_outposts.py
Normal file
20
scripts/check_authentik_outposts.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request("https://authentik.snarfnet.net/api/v3/outposts/instances/", headers=headers)
|
||||||
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
||||||
|
data = json.loads(resp.read().decode())
|
||||||
|
for o in data['results']:
|
||||||
|
print("Outpost:", o['name'], o['type'], o.get('providers'))
|
||||||
|
|
||||||
25
scripts/check_authentik_proxy_providers.py
Normal file
25
scripts/check_authentik_proxy_providers.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import urllib.request, ssl, json
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
AK_BASE = "https://authentik.snarfnet.net"
|
||||||
|
AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
|
||||||
|
def req(endpoint):
|
||||||
|
url = f"{AK_BASE}/api/v3/{endpoint}"
|
||||||
|
r = urllib.request.Request(url, headers={"Authorization": f"Bearer {AK_TOKEN}"})
|
||||||
|
with urllib.request.urlopen(r, timeout=10, context=ctx) as res:
|
||||||
|
return json.loads(res.read().decode())
|
||||||
|
|
||||||
|
print("=== Authentik Providers (Proxy) ===")
|
||||||
|
providers = req("providers/proxy/")
|
||||||
|
for p in providers.get("results", []):
|
||||||
|
print(p["pk"], p["name"], p.get("internal_host"), p.get("external_host"), p.get("mode"))
|
||||||
|
|
||||||
|
print("\n=== Authentik Outposts ===")
|
||||||
|
outposts = req("outposts/instances/")
|
||||||
|
for o in outposts.get("results", []):
|
||||||
|
print(o["pk"], o["name"], o["type"], [p for p in o.get("providers", [])])
|
||||||
|
|
||||||
26
scripts/check_authentik_semaphore.py
Normal file
26
scripts/check_authentik_semaphore.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request("https://authentik.snarfnet.net/api/v3/providers/oauth2/", headers=headers)
|
||||||
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
||||||
|
data = json.loads(resp.read().decode())
|
||||||
|
for p in data['results']:
|
||||||
|
if 'semaphore' in p['name'].lower():
|
||||||
|
print(f"Provider ID: {p['pk']}, Name: {p['name']}")
|
||||||
|
print(f"Redirect URIs: {p.get('redirect_uris')}")
|
||||||
|
print(f"Client ID: {p.get('client_id')}")
|
||||||
|
|
||||||
20
scripts/check_authentik_users.py
Normal file
20
scripts/check_authentik_users.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import urllib.request
|
||||||
|
import ssl
|
||||||
|
import json
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
token = "SnarfNetPermanentAutomationToken2026"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request("https://authentik.snarfnet.net/api/v3/core/users/?page_size=20", headers=headers)
|
||||||
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
||||||
|
data = json.loads(resp.read().decode())
|
||||||
|
for u in data['results']:
|
||||||
|
print(f"User: {u['username']}, Email: {u.get('email')}, Name: {u.get('name')}")
|
||||||
|
|
||||||
16
scripts/check_awx_monitor.py
Normal file
16
scripts/check_awx_monitor.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
pod = run("kubectl get pod -n monitoring -l app.kubernetes.io/name=uptime-kuma -o jsonpath='{.items[0].metadata.name}'")
|
||||||
|
print(run(f"""kubectl exec -n monitoring {pod} -- mariadb --socket=/app/data/run/mariadb.sock -u root -e "USE kuma; SELECT id, name, type, url, port, accepted_statuscodes_json FROM monitor WHERE name LIKE '%AWX%';" """))
|
||||||
|
print(run(f"""kubectl exec -n monitoring {pod} -- mariadb --socket=/app/data/run/mariadb.sock -u root -e "USE kuma; SELECT * FROM heartbeat WHERE monitor_id IN (SELECT id FROM monitor WHERE name LIKE '%AWX%') ORDER BY id DESC LIMIT 5;" """))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
15
scripts/check_bridge_logs.py
Normal file
15
scripts/check_bridge_logs.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker service logs --tail 10 gotify_gotify-discord-bridge")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
15
scripts/check_bridge_map.py
Normal file
15
scripts/check_bridge_map.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("grep -n -A 10 'APP_WEBHOOK_MAP' /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
14
scripts/check_cert1.py
Normal file
14
scripts/check_cert1.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
key_path = r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet"
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=5)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("ls -la /var/lib/docker/volumes/nginx-proxy-manager_nginx-letsencrypt/_data/live/npm-1/")
|
||||||
|
print(stdout.read().decode('utf-8'))
|
||||||
|
c.close()
|
||||||
|
|
||||||
12
scripts/check_cert1_hosts.py
Normal file
12
scripts/check_cert1_hosts.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.71", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'python3 -c "import sqlite3; conn=sqlite3.connect(\'/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite\'); print(conn.execute(\'SELECT id, domain_names, forward_port FROM proxy_host WHERE certificate_id=1;\').fetchall())"'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("Hosts using cert 1:", stdout.read().decode().strip())
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
21
scripts/check_cert_san.py
Normal file
21
scripts/check_cert_san.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
def check_cert(num):
|
||||||
|
cmd = f"docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) openssl x509 -in /etc/letsencrypt/live/{num}/fullchain.pem -noout -text | grep -A 2 'Subject Alternative Name'"
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
print(f"Cert {num}:", stdout.read().decode().strip())
|
||||||
|
|
||||||
|
check_cert("npm-1")
|
||||||
|
check_cert("npm-33")
|
||||||
|
check_cert("npm-37")
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
17
scripts/check_cm_json.py
Normal file
17
scripts/check_cm_json.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
_, o, _ = c.exec_command('kubectl get cm homepage-2-1744068158 -n infrastructure -o json')
|
||||||
|
import json
|
||||||
|
cm = json.loads(o.read().decode())
|
||||||
|
print("Data keys in ConfigMap:", list(cm.get('data', {}).keys()))
|
||||||
|
print("\n--- settings.yaml ---")
|
||||||
|
print(cm.get('data', {}).get('settings.yaml'))
|
||||||
|
print("\n--- widgets.yaml ---")
|
||||||
|
print(cm.get('data', {}).get('widgets.yaml'))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
12
scripts/check_current_clients.py
Normal file
12
scripts/check_current_clients.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.11", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'k3s kubectl exec -n dns deploy/pihole -c pihole -- tail -n 25 /var/log/pihole/pihole.log'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("Queries by client on Aerodrome in last 15 min:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
print("Stderr:", stderr.read().decode())
|
||||||
|
client.close()
|
||||||
21
scripts/check_dcgm_status.py
Normal file
21
scripts/check_dcgm_status.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import paramiko
|
||||||
|
import time
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.90', username='rancher', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(f"echo Katzir476! | sudo -S sh -c 'export KUBECONFIG=/etc/rancher/rke2/rke2.yaml; PATH=$PATH:/var/lib/rancher/rke2/bin; {cmd}'")
|
||||||
|
out = stdout.read().decode().strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
print("Pods status:")
|
||||||
|
print(run("kubectl get pods -n ai-stack -l app.kubernetes.io/name=dcgm-exporter -o wide"))
|
||||||
|
print("Logs from one pod:")
|
||||||
|
pod = run("kubectl get pod -n ai-stack -l app.kubernetes.io/name=dcgm-exporter -o jsonpath='{.items[0].metadata.name}'")
|
||||||
|
print(run(f"kubectl logs -n ai-stack {pod} --tail=20"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
26
scripts/check_deleted_at.py
Normal file
26
scripts/check_deleted_at.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
print(f"\n>>> {cmd}")
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
res = o.read().decode().strip()
|
||||||
|
if res:
|
||||||
|
print(res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
run("""python3 -c "
|
||||||
|
import sqlite3
|
||||||
|
con = sqlite3.connect('/mnt/nfs_shares/heimdall/www/app.sqlite')
|
||||||
|
cur = con.cursor()
|
||||||
|
for r in cur.execute('SELECT id, user_id, title, pinned, \"order\", deleted_at FROM items LIMIT 10;').fetchall():
|
||||||
|
print(r)
|
||||||
|
" """)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
14
scripts/check_deployment_spec.py
Normal file
14
scripts/check_deployment_spec.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print(run("kubectl get deployment grafana -o jsonpath='{.spec.template.spec.containers[0].volumeMounts}'"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
24
scripts/check_direct_apis.py
Normal file
24
scripts/check_direct_apis.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def test(cmd):
|
||||||
|
print(f"\n>>> {cmd}")
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
res = o.read().decode().strip()
|
||||||
|
err = e.read().decode().strip()
|
||||||
|
if res:
|
||||||
|
print(res[:300])
|
||||||
|
if err:
|
||||||
|
print("[stderr]", err[:200])
|
||||||
|
|
||||||
|
test("curl -m 3 -s -I http://140.44.4.28:448/ || echo 'Timeout 140.44.4.28:448'")
|
||||||
|
test("curl -m 3 -s -I http://140.44.4.31:448/ || echo 'Timeout 140.44.4.31:448'")
|
||||||
|
test("curl -m 3 -s http://140.44.4.70:8050/api/v1/devices || echo 'Failed PeaNUT'")
|
||||||
|
test("curl -m 3 -s -H 'Authorization: token 47c10c54574131c007302b246352f653facb5062' http://140.44.4.62:90/api/v1/user || echo 'Failed Gitea'")
|
||||||
|
|
||||||
|
c.close()
|
||||||
12
scripts/check_divloginsso.py
Normal file
12
scripts/check_divloginsso.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- grep -rn "divLoginSso" /opt/technitium/ 2>/dev/null || true')
|
||||||
|
print("grep divLoginSso:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
17
scripts/check_dns_pods_live.py
Normal file
17
scripts/check_dns_pods_live.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
def check_cluster(ip, name):
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(ip, username="root", key_filename=key_path, timeout=5)
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl get pods -n dns -o wide")
|
||||||
|
print(f"Pods on {name} ({ip}):\n", stdout.read().decode())
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
check_cluster("140.44.4.11", "Aerodrome")
|
||||||
|
check_cluster("140.44.4.41", "Spaceport")
|
||||||
|
|
||||||
22
scripts/check_dns_resolutions.py
Normal file
22
scripts/check_dns_resolutions.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
return stdout.read().decode('utf-8', errors='ignore') + stderr.read().decode('utf-8', errors='ignore')
|
||||||
|
|
||||||
|
print("--- DNS check via ns1 (140.44.4.36) ---")
|
||||||
|
print(run("dig @140.44.4.36 +short pihole1.snarfnet.net pihole2.snarfnet.net dns1.snarfnet.net dns2.snarfnet.net"))
|
||||||
|
|
||||||
|
print("--- DNS check via ns2 (140.44.4.65) ---")
|
||||||
|
print(run("dig @140.44.4.65 +short pihole1.snarfnet.net pihole2.snarfnet.net dns1.snarfnet.net dns2.snarfnet.net"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
@@ -1,20 +1,44 @@
|
|||||||
import paramiko
|
import paramiko
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding='utf-8')
|
def check_cluster(host, name):
|
||||||
|
print(f"=== Checking {name} ({host}) ===")
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(host, username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
key_path = r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet"
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl get pods -n dns -o wide')
|
||||||
c = paramiko.SSHClient()
|
print("Pods:")
|
||||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
print(stdout.read().decode())
|
||||||
c.connect("140.44.4.11", username="root", key_filename=key_path, timeout=5)
|
|
||||||
|
|
||||||
stdin, stdout, stderr = c.exec_command("k3s kubectl -n dns get pods,svc")
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole -v')
|
||||||
print("=== PODS & SVCS ===")
|
print("Pihole version:")
|
||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode())
|
||||||
|
|
||||||
stdin, stdout, stderr = c.exec_command("k3s kubectl -n dns describe svc pihole-dns-vip | grep -E 'Events:|Message|Failed'")
|
# Set password explicitly
|
||||||
print("=== SERVICE EVENTS ===")
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole setpassword snarfnetadmin')
|
||||||
print(stdout.read().decode('utf-8'))
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
print("Set password with pihole setpassword:")
|
||||||
|
print(out)
|
||||||
|
if err:
|
||||||
|
print("ERR:", err)
|
||||||
|
|
||||||
c.close()
|
if "Unknown" in out or "invalid" in out.lower() or err:
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole -a -p snarfnetadmin')
|
||||||
|
print("Fallback pihole -a -p:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
# Check Technitium login via API
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/user/login?user=admin&pass=snarfnetadmin"')
|
||||||
|
login_res = stdout.read().decode()
|
||||||
|
print("Technitium API Login:", login_res)
|
||||||
|
|
||||||
|
# Check Technitium SSO config
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl exec -n dns deployment/technitium-dns -- cat /etc/dns/auth.config 2>/dev/null || echo 'No auth.config'")
|
||||||
|
print("Technitium auth.config:")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
|
check_cluster('140.44.4.11', 'Aerodrome')
|
||||||
|
check_cluster('140.44.4.41', 'Spaceport')
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ print("=== SPACEPORT DNS WORKLOADS ===")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
15
scripts/check_docker_tail.py
Normal file
15
scripts/check_docker_tail.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("docker logs --tail 20 opencanary")
|
||||||
|
print("Docker logs:\n", stdout.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
41
scripts/check_down_monitors.py
Normal file
41
scripts/check_down_monitors.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get("http://140.44.4.28:448/api/status-page/heartbeat/default", verify=False, timeout=10)
|
||||||
|
data = r.json()
|
||||||
|
heartbeats = data.get("heartbeatList", {})
|
||||||
|
down = []
|
||||||
|
up = []
|
||||||
|
|
||||||
|
# Also fetch status page monitor list to map IDs to names
|
||||||
|
r_sp = requests.get("http://140.44.4.28:448/api/status-page/default", verify=False, timeout=10)
|
||||||
|
sp_data = r_sp.json()
|
||||||
|
|
||||||
|
id_to_name = {}
|
||||||
|
for group in sp_data.get("publicGroupList", []):
|
||||||
|
for m in group.get("monitorList", []):
|
||||||
|
id_to_name[str(m.get("id"))] = m.get("name")
|
||||||
|
|
||||||
|
for mid, beats in heartbeats.items():
|
||||||
|
name = id_to_name.get(str(mid), f"Monitor #{mid}")
|
||||||
|
if beats:
|
||||||
|
last = beats[-1]
|
||||||
|
if last.get("status") == 1:
|
||||||
|
up.append((name, last.get("ping")))
|
||||||
|
else:
|
||||||
|
down.append((name, last.get("msg")))
|
||||||
|
|
||||||
|
print(f"Total Monitors: {len(heartbeats)}")
|
||||||
|
print(f"Up: {len(up)} | Down: {len(down)}")
|
||||||
|
if down:
|
||||||
|
print("\nDown Monitors:")
|
||||||
|
for name, msg in down:
|
||||||
|
print(f" ❌ {name}: {msg}")
|
||||||
|
else:
|
||||||
|
print("\nAll monitors are UP! 🎉")
|
||||||
|
except Exception as e:
|
||||||
|
print("Error:", e)
|
||||||
|
|
||||||
@@ -19,3 +19,4 @@ print("FILES:")
|
|||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
12
scripts/check_etc_dns.py
Normal file
12
scripts/check_etc_dns.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /etc/dns/')
|
||||||
|
print("/etc/dns listing:")
|
||||||
|
print(stdout.read().decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
|
|
||||||
@@ -11,3 +11,4 @@ c.connect("140.44.4.11", username="root", key_filename=key_path, timeout=5)
|
|||||||
stdin, stdout, stderr = c.exec_command("k3s kubectl -n longhorn-system get middleware authentik-forwardauth -o yaml")
|
stdin, stdout, stderr = c.exec_command("k3s kubectl -n longhorn-system get middleware authentik-forwardauth -o yaml")
|
||||||
print(stdout.read().decode('utf-8'))
|
print(stdout.read().decode('utf-8'))
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|||||||
14
scripts/check_ftl_config.py
Normal file
14
scripts/check_ftl_config.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect('140.44.4.41', username='root', key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("k3s kubectl exec -n dns pihole-88584bd7-d5vq7 -- sed -n '57,85p' /usr/local/bin/pihole")
|
||||||
|
print(stdout.read().decode())
|
||||||
|
|
||||||
|
ssh.close()
|
||||||
22
scripts/check_ftl_db.py
Normal file
22
scripts/check_ftl_db.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.11", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
def exec_cmd(cmd):
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
out = stdout.read().decode()
|
||||||
|
err = stderr.read().decode()
|
||||||
|
return out.strip(), err.strip()
|
||||||
|
|
||||||
|
print("1. Query count in pihole-FTL.db:")
|
||||||
|
out, err = exec_cmd('k3s kubectl exec -n dns deploy/pihole -c pihole -- pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db "SELECT count(*) FROM query_storage;"')
|
||||||
|
print("Count:", out, "Err:", err)
|
||||||
|
|
||||||
|
print("2. FTL integrity check:")
|
||||||
|
out, err = exec_cmd('k3s kubectl exec -n dns deploy/pihole -c pihole -- pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db "PRAGMA integrity_check;"')
|
||||||
|
print("Integrity:", out, "Err:", err)
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
10
scripts/check_ftl_log_spaceport.py
Normal file
10
scripts/check_ftl_log_spaceport.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.41", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'k3s kubectl exec -n dns deploy/pihole -c pihole -- grep -A 10 "Database version is 22" /var/log/pihole/FTL.log'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("Spaceport:\n", stdout.read().decode())
|
||||||
|
client.close()
|
||||||
11
scripts/check_ftl_startup.py
Normal file
11
scripts/check_ftl_startup.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.11", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'k3s kubectl exec -n dns deploy/pihole -c pihole -- grep -B 20 "CRIT: Cannot initialize in-memory database" /var/log/pihole/FTL.log'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print(stdout.read().decode())
|
||||||
|
client.close()
|
||||||
|
|
||||||
11
scripts/check_ftl_version.py
Normal file
11
scripts/check_ftl_version.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.11", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'k3s kubectl exec -n dns deploy/pihole -c pihole -- pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db "SELECT * FROM ftl;"'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print(stdout.read().decode())
|
||||||
|
client.close()
|
||||||
|
|
||||||
11
scripts/check_ftl_version_spaceport.py
Normal file
11
scripts/check_ftl_version_spaceport.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect("140.44.4.41", username="root", key_filename=r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet", timeout=10)
|
||||||
|
|
||||||
|
cmd = 'k3s kubectl exec -n dns deploy/pihole -c pihole -- pihole-FTL sqlite3 /etc/pihole/pihole-FTL.db "SELECT * FROM ftl;"'
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd)
|
||||||
|
print("Spaceport:", stdout.read().decode())
|
||||||
|
client.close()
|
||||||
|
|
||||||
17
scripts/check_gaming_pc.py
Normal file
17
scripts/check_gaming_pc.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print("Ping 140.44.4.80:")
|
||||||
|
print(run("ping -c 2 -W 1 140.44.4.80 || true"))
|
||||||
|
print("ARP table for bc:24:11:67:16:cd:")
|
||||||
|
print(run("ip neigh | grep -i 'bc:24:11:67:16:cd' || arp -a | grep -i 'bc:24:11:67:16:cd' || true"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
19
scripts/check_glances_host.py
Normal file
19
scripts/check_glances_host.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
print("ps aux | grep glances:")
|
||||||
|
print(run("ps aux | grep glances"))
|
||||||
|
|
||||||
|
print("\nnetstat / ss listening:")
|
||||||
|
print(run("ss -tulpn | grep 61208 || ss -tulpn | grep glances"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
21
scripts/check_gotify_apps.py
Normal file
21
scripts/check_gotify_apps.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
return stdout.read().decode('utf-8', errors='ignore').strip()
|
||||||
|
|
||||||
|
print("Gotify database check:")
|
||||||
|
print(run("find /var/lib/docker/volumes/ -name 'gotify.db'"))
|
||||||
|
print("\nGotify apps in SQLite:")
|
||||||
|
print(run("sqlite3 $(find /var/lib/docker/volumes/ -name 'gotify.db') 'SELECT id, name, token FROM application;'"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
28
scripts/check_gotify_apps_py.py
Normal file
28
scripts/check_gotify_apps_py.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
py_script = """
|
||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||||
|
print('Tables:', cur.fetchall())
|
||||||
|
for tbl in cur.fetchall():
|
||||||
|
pass
|
||||||
|
cur.execute("SELECT * FROM application")
|
||||||
|
print('Applications:', cur.fetchall())
|
||||||
|
"""
|
||||||
|
stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_script}"')
|
||||||
|
print(stdout.read().decode())
|
||||||
|
err = stderr.read().decode()
|
||||||
|
if err: print("Stderr:", err)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
18
scripts/check_gotify_data.py
Normal file
18
scripts/check_gotify_data.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
return stdout.read().decode('utf-8', errors='ignore').strip()
|
||||||
|
|
||||||
|
print(run("ls -la /var/lib/docker/volumes/gotify_gotify_data/_data/; file /var/lib/docker/volumes/gotify_gotify_data/_data/*"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
13
scripts/check_gotify_files.py
Normal file
13
scripts/check_gotify_files.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
return o.read().decode().strip()
|
||||||
|
|
||||||
|
print(run("ls -la /mnt/swarm_shared/legacy_volumes/gotify/_data/"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
22
scripts/check_gotify_idx.py
Normal file
22
scripts/check_gotify_idx.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
py_script = """import sqlite3
|
||||||
|
conn = sqlite3.connect('/mnt/swarm_shared/legacy_volumes/gotify/gotify.db')
|
||||||
|
cur = conn.cursor()
|
||||||
|
indexes = cur.execute("SELECT name, sql FROM sqlite_master WHERE type='index' AND tbl_name='applications'").fetchall()
|
||||||
|
for idx in indexes:
|
||||||
|
print(idx)
|
||||||
|
"""
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command("cat > /tmp/check_idx.py && python3 /tmp/check_idx.py")
|
||||||
|
stdin.write(py_script)
|
||||||
|
stdin.close()
|
||||||
|
print(stdout.read().decode())
|
||||||
|
print(stderr.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
16
scripts/check_gotify_mount.py
Normal file
16
scripts/check_gotify_mount.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
print("Gotify inspect mounts:")
|
||||||
|
print(run("docker service inspect gotify_gotify --format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}'"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
15
scripts/check_gotify_msgs.py
Normal file
15
scripts/check_gotify_msgs.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
stdin, stdout, stderr = c.exec_command('sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db "SELECT id, appid, title, message FROM messages ORDER BY id DESC LIMIT 5;"')
|
||||||
|
print("Recent messages in Gotify:\n", stdout.read().decode())
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
19
scripts/check_gotify_service.py
Normal file
19
scripts/check_gotify_service.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect('140.44.4.71', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
_, o, e = c.exec_command(cmd)
|
||||||
|
out = o.read().decode().strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
print("Gotify service ps:")
|
||||||
|
print(run("docker service ps gotify_gotify --no-trunc | head -n 10"))
|
||||||
|
|
||||||
|
print("\nGotify service logs:")
|
||||||
|
print(run("docker service logs gotify_gotify --tail=25"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
21
scripts/check_gotify_tables.py
Normal file
21
scripts/check_gotify_tables.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import paramiko
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
|
||||||
|
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
stdin, stdout, stderr = c.exec_command(cmd)
|
||||||
|
return stdout.read().decode('utf-8', errors='ignore').strip()
|
||||||
|
|
||||||
|
print("Gotify tables:")
|
||||||
|
print(run("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db '.tables'"))
|
||||||
|
print("\nApplications:")
|
||||||
|
print(run("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT * FROM applications;'"))
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
37
scripts/check_gpus_idrac.py
Normal file
37
scripts/check_gpus_idrac.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import paramiko
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
"Node 1 (140.44.44.121)": "140.44.44.121",
|
||||||
|
"Node 2 (140.44.44.122)": "140.44.44.122",
|
||||||
|
"Node 3 (140.44.44.123)": "140.44.44.123",
|
||||||
|
"Node 4 (140.44.44.124)": "140.44.44.124",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, ip in nodes.items():
|
||||||
|
try:
|
||||||
|
c = paramiko.SSHClient()
|
||||||
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
c.connect(ip, username="root", password="Katzir476!", timeout=10)
|
||||||
|
|
||||||
|
# Check PCIe inventory for NVIDIA
|
||||||
|
stdin, stdout, stderr = c.exec_command("racadm hwinventory")
|
||||||
|
out = stdout.read().decode(errors="ignore")
|
||||||
|
|
||||||
|
gpus = []
|
||||||
|
for block in out.split("[InstanceID:"):
|
||||||
|
if "NVIDIA" in block or "Tesla" in block or "T4" in block:
|
||||||
|
lines = [l.strip() for l in block.splitlines() if any(k in l for k in ["DeviceDescription", "DeviceType", "FQDD", "PCI"])]
|
||||||
|
gpus.append(" | ".join(lines[:3]))
|
||||||
|
|
||||||
|
# Check Above 4G decoding
|
||||||
|
stdin, stdout, stderr = c.exec_command("racadm get bios.IntegratedDevices.MemOpMode || racadm get bios.IntegratedDevices.MemoryMappedIoAbove4GB")
|
||||||
|
a4g = stdout.read().decode(errors="ignore").strip()
|
||||||
|
|
||||||
|
print(f"=== {name} ===")
|
||||||
|
print(f" GPUs detected: {len(gpus)}")
|
||||||
|
for g in gpus:
|
||||||
|
print(f" {g}")
|
||||||
|
c.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"=== {name} === ERROR: {e}")
|
||||||
|
|
||||||
27
scripts/check_grafana_api.py
Normal file
27
scripts/check_grafana_api.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
auth = ('admin', 'd0KJLZJXUrbIhW1sjU8lrdFYj7VXts26V7Jw7xQr')
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get('https://grafana.snarfnet.net/api/datasources', auth=auth, verify=False, timeout=10)
|
||||||
|
print("Datasources Status:", r.status_code)
|
||||||
|
if r.status_code == 200:
|
||||||
|
for ds in r.json():
|
||||||
|
print(f" - ID: {ds.get('id')}, Name: {ds.get('name')}, Type: {ds.get('type')}, URL: {ds.get('url')}, isDefault: {ds.get('isDefault')}")
|
||||||
|
except Exception as e:
|
||||||
|
print("Error getting datasources:", e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get('https://grafana.snarfnet.net/api/search', auth=auth, verify=False, timeout=10)
|
||||||
|
print("Dashboards Status:", r.status_code)
|
||||||
|
if r.status_code == 200:
|
||||||
|
dashboards = r.json()
|
||||||
|
print(f"Total dashboards: {len(dashboards)}")
|
||||||
|
for d in dashboards[:15]:
|
||||||
|
print(f" - {d.get('title')} (uri: {d.get('uri')}, type: {d.get('type')})")
|
||||||
|
except Exception as e:
|
||||||
|
print("Error getting dashboards:", e)
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user