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
103 lines
2.9 KiB
JavaScript
103 lines
2.9 KiB
JavaScript
import { execFileSync } from 'child_process';
|
|
|
|
const BW = 'bw';
|
|
const ORG_ID = '4bd75130-b4d3-48d4-a4cb-e52b70295a51';
|
|
const AI_COLLECTION = '5be27a82-8475-4c38-96b2-fa94ec8c957b';
|
|
|
|
let _session = null;
|
|
|
|
function bwEnv() {
|
|
const env = { ...process.env };
|
|
for (const k of ['HTTPS_PROXY','HTTP_PROXY','ALL_PROXY','https_proxy','http_proxy','all_proxy'])
|
|
delete env[k];
|
|
env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
return env;
|
|
}
|
|
|
|
function run(args, input) {
|
|
return execFileSync(BW, args, {
|
|
env: bwEnv(),
|
|
encoding: 'utf8',
|
|
input,
|
|
stdio: input ? ['pipe','pipe','pipe'] : ['ignore','pipe','pipe'],
|
|
}).trim();
|
|
}
|
|
|
|
export async function initVaultwarden() {
|
|
const email = process.env.BW_EMAIL || 'allogn@gmail.com';
|
|
const password = process.env.BW_PASSWORD;
|
|
|
|
// Data dir is mounted from host — server already configured, skip bw config server
|
|
|
|
let status = 'unauthenticated';
|
|
try {
|
|
status = JSON.parse(run(['status'])).status;
|
|
} catch {}
|
|
|
|
if (status === 'unauthenticated') {
|
|
run(['login', email, password, '--raw']);
|
|
}
|
|
|
|
_session = run(['unlock', password, '--raw']);
|
|
run(['sync', '--session', _session]);
|
|
console.log('Vaultwarden: ready');
|
|
}
|
|
|
|
function session() {
|
|
if (!_session) throw new Error('Vaultwarden not initialized');
|
|
return _session;
|
|
}
|
|
|
|
export function vwGetPassword(name) {
|
|
return run(['get', 'password', name, '--session', session()]);
|
|
}
|
|
|
|
export function vwGetItem(name) {
|
|
return JSON.parse(run(['get', 'item', name, '--session', session()]));
|
|
}
|
|
|
|
export function vwListItems(search) {
|
|
const args = ['list', 'items', '--session', session()];
|
|
if (search) args.push('--search', search);
|
|
return JSON.parse(run(args));
|
|
}
|
|
|
|
export function vwListOrgItems(search) {
|
|
const args = ['list', 'items', '--organizationid', ORG_ID, '--session', session()];
|
|
if (search) args.push('--search', search);
|
|
return JSON.parse(run(args));
|
|
}
|
|
|
|
export function vwCreateLogin({ name, username, password, url, notes }) {
|
|
const item = {
|
|
organizationId: ORG_ID,
|
|
collectionIds: [AI_COLLECTION],
|
|
folderId: null,
|
|
type: 1,
|
|
name,
|
|
notes: notes || null,
|
|
favorite: false,
|
|
login: {
|
|
username: username || null,
|
|
password,
|
|
uris: url ? [{ match: null, uri: url }] : [],
|
|
},
|
|
};
|
|
const encoded = run(['encode'], JSON.stringify(item));
|
|
return JSON.parse(run(['create', 'item', encoded, '--session', session()]));
|
|
}
|
|
|
|
export function vwUpdatePassword(nameOrId, newPassword) {
|
|
let item;
|
|
try {
|
|
item = JSON.parse(run(['get', 'item', nameOrId, '--session', session()]));
|
|
} catch {
|
|
const items = vwListOrgItems(nameOrId);
|
|
item = items.find(i => i.name === nameOrId);
|
|
if (!item) throw new Error(`Item not found: ${nameOrId}`);
|
|
}
|
|
item.login.password = newPassword;
|
|
const encoded = run(['encode'], JSON.stringify(item));
|
|
return JSON.parse(run(['edit', 'item', item.id, encoded, '--session', session()]));
|
|
}
|