[pve] Weekly backup 2026-06-08 — 42 files changed, 869 insertions(+)

This commit is contained in:
Proxmox Backup
2026-06-08 22:46:05 -05:00
parent 3daf25fdeb
commit 81fc88085e
42 changed files with 869 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)" >>/var/log/update-lxcs-cron.log 2>/dev/null
0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)" >>/var/log/update-lxcs-cron.log 2>/dev/null
* * * * * /usr/bin/python3 /usr/local/bin/jarvis-ping-probe.py >> /var/log/jarvis-ping-probe.log 2>&1
*/3 * * * * /usr/local/bin/jarvis-netscan.sh >>/var/log/jarvis-netscan.log 2>&1
* * * * * /usr/local/bin/jarvis-phone-probe.sh >/dev/null 2>&1
0 3 * * 0 /usr/local/bin/proxmox-backup >> /var/log/proxmox-backup.log 2>&1
+1
View File
@@ -0,0 +1 @@
pve
+11
View File
@@ -0,0 +1,11 @@
127.0.0.1 localhost.localdomain localhost
10.48.200.90 pve.orbishsoting.com pve
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
+33
View File
@@ -0,0 +1,33 @@
# network interface settings; autogenerated
# Please do NOT modify this file directly, unless you know what
# you're doing.
#
# If you want to manage parts of the network configuration manually,
# please utilize the 'source' or 'source-directory' directives to do
# so.
# PVE will preserve these directives, but will NOT read its network
# configuration from sourced files, so do not attempt to move any of
# the PVE managed interfaces into external files!
auto lo
iface lo inet loopback
iface nic0 inet manual
iface nic1 inet manual
iface nic2 inet manual
iface nic3 inet manual
auto vmbr0
iface vmbr0 inet static
address 10.48.200.90/24
gateway 10.48.200.1
bridge-ports nic0
bridge-stp off
bridge-fd 0
bridge-vlan-aware yes
bridge-vids 2-4094
source /etc/network/interfaces.d/*
View File
+7
View File
@@ -0,0 +1,7 @@
# resolv.conf(5) file generated by tailscale
# For more info, see https://tailscale.com/s/resolvconf-overwrite
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN
nameserver 100.100.100.100
nameserver fd7a:115c:a1e0::53
search tail7dfd0c.ts.net
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# JARVIS Network Scanner — runs on PVE1, pushes nmap results to JARVIS
# Cron: */3 * * * * /usr/local/bin/jarvis-netscan.sh >/dev/null 2>&1
JARVIS_URL="https://165.22.1.228"
JARVIS_HOST="jarvis.orbishosting.com"
REG_KEY="f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518"
SUBNET="10.48.200.0/24"
TMPFILE=$(mktemp)
nmap -sn --send-ip "$SUBNET" 2>/dev/null > "$TMPFILE"
if [ ! -s "$TMPFILE" ]; then
echo "$(date): nmap produced no output" >&2
rm -f "$TMPFILE"
exit 1
fi
JSON=$(python3 - "$TMPFILE" <<'PYEOF'
import sys, re, json
with open(sys.argv[1]) as f:
data = f.read()
devices = []
cur = None
for line in data.splitlines():
line = line.strip()
m = re.match(r'Nmap scan report for (?:(\S+) \()?(\d+\.\d+\.\d+\.\d+)\)?', line)
if m:
if cur:
devices.append(cur)
hn = m.group(1) if m.group(1) and m.group(1) != m.group(2) else ''
cur = {'ip': m.group(2), 'hostname': hn, 'mac': '', 'vendor': ''}
elif cur:
m2 = re.match(r'MAC Address: ([0-9A-Fa-f:]{17}) \(([^)]+)\)', line)
if m2:
cur['mac'] = m2.group(1).lower()
cur['vendor'] = '' if m2.group(2) == 'Unknown' else m2.group(2)
if cur:
devices.append(cur)
print(json.dumps({'devices': devices}))
PYEOF
)
rm -f "$TMPFILE"
if [ -z "$JSON" ]; then
echo "$(date): JSON parse failed" >&2
exit 1
fi
RESPONSE=$(curl -sk --max-time 15 \
-X POST "$JARVIS_URL/api/netscan" \
-H "Host: $JARVIS_HOST" \
-H "Content-Type: application/json" \
-H "X-Registration-Key: $REG_KEY" \
-d "$JSON" 2>/dev/null)
echo "$(date): $RESPONSE"
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
# JARVIS VoIP Phone Probe — runs every minute on PVE1
# Pings all Yealink phones + checks FusionPBX SIP registration (read-only)
# 200.3 is on an external FusionPBX — ping only, no SIP check
JARVIS_URL="https://165.22.1.228"
JARVIS_HOST="jarvis.orbishosting.com"
REG_KEY="f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518"
FUSION_HOST="134.209.72.226"
# IP|alias|extension(none=skip SIP check)|mac
PHONES=(
"10.48.200.2|Yealink — Myron Main (Ext 1000)|1000|80:5e:c0:35:04:77"
"10.48.200.3|Yealink — United Mirror & Glass (External SIP)|none|c4:fc:22:28:63:71"
"10.48.200.43|Yealink T48S — Tommy Main (Ext 1001)|1001|80:5e:0c:15:0c:4f"
"10.48.200.86|Yealink — Myron Vanguard WiFi (Offline During Work Hrs)|none|"
"10.48.200.65|Yealink — Myron Vanguard Work (Ext 1003)|1003|c4:fc:22:13:e1:89"
)
# Get SIP registrations from FusionPBX (read-only)
REG_OUTPUT=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes \
root@$FUSION_HOST "fs_cli -x 'show registrations'" 2>/dev/null || echo "")
DEVICES="["
FIRST=1
for PHONE in "${PHONES[@]}"; do
IFS='|' read -r IP ALIAS EXT MAC <<< "$PHONE"
# Ping probe
if ping -c 1 -W 2 "$IP" > /dev/null 2>&1; then
STATUS="online"
else
STATUS="offline"
fi
# SIP check — skip for external phones (ext=none)
if [ "$EXT" = "none" ]; then
SIP="external"
elif [ -n "$REG_OUTPUT" ] && echo "$REG_OUTPUT" | grep -q "^${EXT},"; then
SIP="registered"
else
SIP="unregistered"
fi
[ $FIRST -eq 0 ] && DEVICES+=","
DEVICES+="{\"ip\":\"$IP\",\"alias\":\"$ALIAS\",\"mac\":\"$MAC\",\"vendor\":\"Yealink\",\"status\":\"$STATUS\",\"sip_status\":\"$SIP\",\"extension\":\"$EXT\"}"
FIRST=0
done
DEVICES+="]"
curl -sk --max-time 10 \
-X POST "$JARVIS_URL/api/netscan" \
-H "Host: $JARVIS_HOST" \
-H "Content-Type: application/json" \
-H "X-Registration-Key: $REG_KEY" \
-d "{\"devices\":$DEVICES}" > /dev/null 2>&1
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
JARVIS Ping Probe — runs on PVE1 (10.48.200.90), which is on the LAN.
Pings devices that can't run the full agent, then calls JARVIS heartbeat
on their behalf so the dashboard shows live status.
"""
import json
import subprocess
import urllib.request
import urllib.error
import ssl
JARVIS_URL = "https://165.22.1.228"
HOST_HEADER = "jarvis.orbishosting.com"
# Devices to probe: agent_id → api_key
DEVICES = {
"fortigate_gw": "00103aea6fcbf837bc55e11b445a3620",
"yealink_t48s": "2bf8bd7ca8dd31c28fd16aa956e15f88",
"homeassistant_ha": "6f8077dee7a7b4af202bc80886f1223d",
}
# Map agent_id → IP (for ping)
IPS = {
"fortigate_gw": "10.48.200.1",
"yealink_t48s": "10.48.200.43",
"homeassistant_ha": "10.48.200.97",
}
def ping(ip: str) -> bool:
result = subprocess.run(
["ping", "-c", "1", "-W", "2", ip],
capture_output=True, timeout=5
)
return result.returncode == 0
def heartbeat(agent_id: str, api_key: str, alive: bool):
# If device is down we still send heartbeat so JARVIS updates last_seen
# and sets status based on the alive flag via the metric payload
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
payload = json.dumps({}).encode()
req = urllib.request.Request(
f"{JARVIS_URL}/api/agent/heartbeat",
data=payload, method="POST"
)
req.add_header("Content-Type", "application/json")
req.add_header("X-Agent-Key", api_key)
req.add_header("Host", HOST_HEADER)
try:
with urllib.request.urlopen(req, timeout=10, context=ctx):
pass
except Exception:
pass
def update_status(agent_id: str, api_key: str, status: str):
"""Push a minimal metric so JARVIS knows if device is up or down."""
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
payload = json.dumps({
"type": "system",
"data": {
"hostname": agent_id,
"cpu_percent": 0,
"ping_only": True,
"ping_status": status,
}
}).encode()
req = urllib.request.Request(
f"{JARVIS_URL}/api/agent/metrics",
data=payload, method="POST"
)
req.add_header("Content-Type", "application/json")
req.add_header("X-Agent-Key", api_key)
req.add_header("Host", HOST_HEADER)
try:
with urllib.request.urlopen(req, timeout=10, context=ctx):
pass
except Exception:
pass
def main():
for agent_id, api_key in DEVICES.items():
ip = IPS.get(agent_id, "")
alive = ping(ip) if ip else False
status = "online" if alive else "offline"
print(f"{agent_id} ({ip}): {status}", flush=True)
heartbeat(agent_id, api_key, alive)
if alive:
update_status(agent_id, api_key, status)
if __name__ == "__main__":
main()
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
WEB_JS=/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
if [ -s "$WEB_JS" ] && ! grep -q NoMoreNagging "$WEB_JS"; then
echo "Patching Web UI nag..."
sed -i -e "/data\.status/ s/!//" -e "/data\.status/ s/active/NoMoreNagging/" "$WEB_JS"
fi
MOBILE_TPL=/usr/share/pve-yew-mobile-gui/index.html.tpl
MARKER="<!-- MANAGED BLOCK FOR MOBILE NAG -->"
if [ -f "$MOBILE_TPL" ] && ! grep -q "$MARKER" "$MOBILE_TPL"; then
echo "Patching Mobile UI nag..."
printf "%s\n" \
"$MARKER" \
"<script>" \
" function removeSubscriptionElements() {" \
" // --- Remove subscription dialogs ---" \
" const dialogs = document.querySelectorAll('dialog.pwt-outer-dialog');" \
" dialogs.forEach(dialog => {" \
" const text = (dialog.textContent || '').toLowerCase();" \
" if (text.includes('subscription')) {" \
" dialog.remove();" \
" console.log('Removed subscription dialog');" \
" }" \
" });" \
"" \
" // --- Remove subscription cards, but keep Reboot/Shutdown/Console ---" \
" const cards = document.querySelectorAll('.pwt-card.pwt-p-2.pwt-d-flex.pwt-interactive.pwt-justify-content-center');" \
" cards.forEach(card => {" \
" const text = (card.textContent || '').toLowerCase();" \
" const hasButton = card.querySelector('button');" \
" if (!hasButton && text.includes('subscription')) {" \
" card.remove();" \
" console.log('Removed subscription card');" \
" }" \
" });" \
" }" \
"" \
" const observer = new MutationObserver(removeSubscriptionElements);" \
" observer.observe(document.body, { childList: true, subtree: true });" \
" removeSubscriptionElements();" \
" setInterval(removeSubscriptionElements, 300);" \
" setTimeout(() => {observer.disconnect();}, 10000);" \
"</script>" \
"" >> "$MOBILE_TPL"
fi
+4
View File
@@ -0,0 +1,4 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDNYf4z78s4+K4HoiUiHoqPbCCEFngCAXKP7mGwhCAc86TTbAosTpAnG1HycPzb2s2B3K3geQPUzQzcLvWdBX8hJM8IamvAZ/WXPtOszWTuVnaYM6BQY7ldIdXi3a+xscWr+M8dM6OexXqdCAy66HXmgl98+Cg2uEbxCFelH81/5d3cuoCXllpvUawyYZe5UjFjPeBpPc/QyhDxG4ovYYpcCeHbzLXc9jIfawjwJTDcYfeXVHFisMdSUp0+eXndRM1TybeSOfT4oQbuijdsy4IQo0md5fRYgZuXxHMIgy7obNB3OPf9szgbWTEWK6jNFhkQHIZXPSRxSM9L1a0RkarQk+xqTf96wTJL/Uz6hSyImYjhtPvcOoBRejaQaK96HuWGe3At96+I6WjvJNEDM/jF9tosp2nbdhcGRitYmxREdv7M8AYM393MKT94BBrulr6tI504+0dDTH7IaojYc8SBAtu1TrUwinLA9zQ35Ney5Ry/Mr7tNOLU1Ni3lkqNRWysEjWxEizM/1sK7u2fbAzx3kE+TRpyzmFv6gSiGHqjs5j/tG6daK7Hv6OvbHSWwV/pW6CKslJWFAsa5tVv+Fw8cXdcMMyb6/CYTUtcFMgcF0hhtsd1g6YfnOWRGLcUxFe9odiayhMlstne/dqeyvQCjStrzOxUT5ta9L9JZifDkQ== root@pve
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCbvKQjreU0F6AWjjcJ76l6mAUpea/VxVwMZ5Yu/0kd4rnAIU02vzoc0jGkNs3q8PiBqBfnrvPJH9TJIzqAiIE/CKxP4VVyVQj9pCIDurIVLW2tkt/GTurRMt6K2GO6HvssIOapw+j07Q/ERGg6frmctZ7VlV73YaOz285FC8J+A7CYSQxcssDkUWWMgVENv89/sqJh7xLUA71qhdr8Yg9rzjXTOn+QQRSBt+c3iSM8B0t7n3GsAQcHV6CisctL9pARG/2PtnShjPhZSqBIQuYqLl5iOnYIs6mh80DcXpfPcUzqk3FRV97CwtkkGPIxFUkQInW5bO/VhNHtfZJ0nPhsXQbVxUrIaSFHNjKZCfY1N0ngFrwTndlrrtMhDtebjxwNbjskICxT4qVYqYPasnl0VUaUrPzkx5qzr1Gg2EkweytlzzQDLO0wfGd+LoDzoW75sVoQz7VxbfIYgdm07H1dgp+EIyT2uOGJctew8KDpE7bQKdRXVmJ8QwslTu4RFZwryhwtRvYotgO+bA0j7cbNxprUWGkPP5FVmJhPlQMxHTEOuRurX9Z8Guz8w1XlpbV6jB+ZcZhN/SQ6Nr4qFY1SkxszkyiwcSVkxNns1EWnyjm6xfKCGp7nxfHjJ/HmXP/Jq7jcBwyRIE/dAP7Zx6AbbhembRyOc0JnNCzRZg/DWQ== root@pve2
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC90VSAwyxC6dYkhfS4cZOFRB/OhCXU+06zHxB2NQNOOnozY9n3FvwgHgOUSO+4tEX6F5uGswzpNLuA/rDMdUfmWt6n/i/H98RlxeyNDd0a5fqApHVVKuZRyrSIDrWHfMRfOCv9PJw1ctZ77P46yOXn/2i39vJfqXOi0Tu60SkJGVKH2Hk7XT9qL1/8MSq/1qgNXQQAt+v0Lk2tTDMlAZhPvOnvwwrG9CwPKFlEKS6pS57nDSco1KHuqKhVTGbdrLrqEb9he4z/bYBMeiEh1odi2FOgeoM6pp2DawlST0jxcPQPOMcnC96atWQyqMspied2tHPtwA1aZwgJBqE6uUgA1JMCo8AIcVqLIBHDbco86L46fRDFOr5eoItN8Kxzn0HnZy3FPahgnUdMyZMmpgdaOrktcslsp2UVZY68jVpbo+OnSqmtqdRlMeox+rn3dYxc4QnV7NzmEC0m0joWzk3JPV21R5HSWcBXuuoIN03EELPK+HX4UYPdoo6RsUh6Q2d0a0uGPaRuQP9Er8einWuQAhGsNZQy2GLgDvJd0osW5saMYU4yzN78BDacix0xQc8U6TS8NIRwv2crbJryqq1IuWDaj02NEw47e90/Gp10ozZHyLQNIeGKrj435CDIAPu3C1/Dfonx6Y08iW/aWKvmIah5VzHERTJ1/p2OmrHYJw== root@pve2
+1
View File
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDNYf4z78s4+K4HoiUiHoqPbCCEFngCAXKP7mGwhCAc86TTbAosTpAnG1HycPzb2s2B3K3geQPUzQzcLvWdBX8hJM8IamvAZ/WXPtOszWTuVnaYM6BQY7ldIdXi3a+xscWr+M8dM6OexXqdCAy66HXmgl98+Cg2uEbxCFelH81/5d3cuoCXllpvUawyYZe5UjFjPeBpPc/QyhDxG4ovYYpcCeHbzLXc9jIfawjwJTDcYfeXVHFisMdSUp0+eXndRM1TybeSOfT4oQbuijdsy4IQo0md5fRYgZuXxHMIgy7obNB3OPf9szgbWTEWK6jNFhkQHIZXPSRxSM9L1a0RkarQk+xqTf96wTJL/Uz6hSyImYjhtPvcOoBRejaQaK96HuWGe3At96+I6WjvJNEDM/jF9tosp2nbdhcGRitYmxREdv7M8AYM393MKT94BBrulr6tI504+0dDTH7IaojYc8SBAtu1TrUwinLA9zQ35Ney5Ry/Mr7tNOLU1Ni3lkqNRWysEjWxEizM/1sK7u2fbAzx3kE+TRpyzmFv6gSiGHqjs5j/tG6daK7Hv6OvbHSWwV/pW6CKslJWFAsa5tVv+Fw8cXdcMMyb6/CYTUtcFMgcF0hhtsd1g6YfnOWRGLcUxFe9odiayhMlstne/dqeyvQCjStrzOxUT5ta9L9JZifDkQ== root@pve
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=FileBrowser Quantum
After=network.target
[Service]
User=root
WorkingDirectory=/usr/local/community-scripts
ExecStart=/usr/local/bin/filebrowser -c /usr/local/community-scripts/fq-config.yaml
Restart=always
[Install]
WantedBy=multi-user.target
+15
View File
@@ -0,0 +1,15 @@
[Unit]
Description=JARVIS Monitoring Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/jarvis-agent/jarvis-agent.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=Ollama Service
After=network-online.target
[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
[Install]
WantedBy=default.target
+20
View File
@@ -0,0 +1,20 @@
# vzdump default settings
#tmpdir: DIR
#dumpdir: DIR
#storage: STORAGE_ID
#mode: snapshot|suspend|stop
#bwlimit: KBPS
#performance: [max-workers=N][,pbs-entries-max=N]
#ionice: PRI
#lockwait: MINUTES
#stopwait: MINUTES
#stdexcludes: BOOLEAN
#mailto: ADDRESSLIST
#prune-backups: keep-INTERVAL=N[,...]
#script: FILENAME
#exclude-path: PATHLIST
#pigz: N
#notes-template: {{guestname}}
#pbs-change-detection-mode: legacy|data|metadata
#fleecing: enabled=BOOLEAN,storage=STORAGE_ID