Sync infra config: HA/Zabbix relocation, Immich storage move, new services

Accumulated uncommitted infra changes:
- Caddyfile: repoint HA/Zabbix to 192.168.1.4/.3, add ~20 new site routes
- Immich: move media to /mnt/smsg, enable CUDA ML, mem limits, rewrite backup.sh
- Add service stacks: agap-mcp, anki, family, freshrss, iperf3, kanboard,
  linkwarden, qbittorrent, radicale, syncthing, vikunja, windows
- openwebui: enable API keys; ollama: drop CPU fallback
- seafile/zabbix: extra_hosts entries; matrix: add user juris
- Remove pihole stack and stale wiki/migrate.py
- Ignore marketplace-mcp (standalone repo) and linkwarden runtime data

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
Alvis
2026-07-04 13:28:04 +00:00
parent 4363130163
commit b8efe4732d
51 changed files with 2813 additions and 374 deletions

View File

@@ -0,0 +1,45 @@
const BASE = () => process.env.HA_URL || 'http://192.168.1.4:8123';
let _token = null;
export function initHA(token) {
_token = token;
console.log('Home Assistant: ready');
}
function token() {
if (!_token) throw new Error('HA not initialized');
return _token;
}
async function api(path, opts = {}) {
const res = await fetch(`${BASE()}/api${path}`, {
...opts,
headers: { Authorization: `Bearer ${token()}`, 'Content-Type': 'application/json', ...opts.headers },
});
if (!res.ok) throw new Error(`HA API ${path}: ${res.status}`);
return res.json();
}
export async function haGetState(entityId) {
const s = await api(`/states/${entityId}`);
return { entity_id: s.entity_id, state: s.state, attributes: s.attributes, last_changed: s.last_changed };
}
export async function haListEntities(domain) {
const states = await api('/states');
const filtered = domain ? states.filter(s => s.entity_id.startsWith(`${domain}.`)) : states;
return filtered.map(s => ({ entity_id: s.entity_id, state: s.state, friendly_name: s.attributes.friendly_name }));
}
export async function haCallService(domain, service, data = {}) {
return api(`/services/${domain}/${service}`, {
method: 'POST',
body: JSON.stringify(data),
});
}
export async function haGetHistory(entityId, hours = 24) {
const start = new Date(Date.now() - hours * 3600 * 1000).toISOString();
const data = await api(`/history/period/${start}?filter_entity_id=${entityId}&minimal_response=true`);
return data[0] || [];
}