614 lines
24 KiB
JavaScript
614 lines
24 KiB
JavaScript
import fs from 'node:fs';
|
|
import { path } from './utils/pathUtils.js';
|
|
import { DatabaseSync } from 'node:sqlite';
|
|
import bcrypt from 'bcryptjs';
|
|
import { nanoid } from 'nanoid';
|
|
import { config } from './config.js';
|
|
import { settingDefinitions } from './settingsRegistry.js';
|
|
|
|
fs.mkdirSync(path.dirname(config.databasePath), { recursive: true });
|
|
fs.mkdirSync(config.fileLockerDir, { recursive: true });
|
|
fs.mkdirSync(config.toolsDir, { recursive: true });
|
|
|
|
export const db = new DatabaseSync(config.databasePath);
|
|
db.exec('PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;');
|
|
|
|
export function id(prefix) {
|
|
return `${prefix}_${nanoid(12)}`;
|
|
}
|
|
|
|
export function now() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
export function json(value, fallback = []) {
|
|
if (value == null || value === '') return JSON.stringify(fallback);
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
export function parseJson(value, fallback = []) {
|
|
try {
|
|
return value ? JSON.parse(value) : fallback;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function ensureColumn(table, column, definition) {
|
|
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
if (!columns.some((row) => row.name === column)) {
|
|
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
}
|
|
}
|
|
|
|
export function migrate() {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
email TEXT NOT NULL UNIQUE,
|
|
display_name TEXT NOT NULL,
|
|
password_hash TEXT,
|
|
auth_provider TEXT NOT NULL DEFAULT 'local',
|
|
role TEXT NOT NULL DEFAULT 'user',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS groups (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
description TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_groups (
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
group_id TEXT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (user_id, group_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS credentials (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
username TEXT,
|
|
secret_cipher TEXT NOT NULL,
|
|
secret_iv TEXT NOT NULL,
|
|
secret_tag TEXT NOT NULL,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hosts (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
fqdn TEXT,
|
|
address TEXT NOT NULL,
|
|
os_family TEXT NOT NULL DEFAULT 'windows',
|
|
transport TEXT NOT NULL DEFAULT 'winrm',
|
|
port INTEGER,
|
|
credential_id TEXT REFERENCES credentials(id) ON DELETE SET NULL,
|
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
notes TEXT,
|
|
source_type TEXT NOT NULL DEFAULT 'manual',
|
|
source_connection_id TEXT,
|
|
source_ref TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS vcenter_connections (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
base_url TEXT NOT NULL,
|
|
username TEXT,
|
|
secret_cipher TEXT,
|
|
secret_iv TEXT,
|
|
secret_tag TEXT,
|
|
credential_id TEXT REFERENCES credentials(id) ON DELETE SET NULL,
|
|
target_type TEXT NOT NULL DEFAULT 'vcenter',
|
|
api_mode TEXT NOT NULL DEFAULT 'auto',
|
|
request_timeout_ms INTEGER NOT NULL DEFAULT 20000,
|
|
allow_untrusted_tls INTEGER NOT NULL DEFAULT 0,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
last_test_status TEXT,
|
|
last_test_message TEXT,
|
|
last_test_at TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'shared',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS host_groups (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
mode TEXT NOT NULL DEFAULT 'manual',
|
|
match_mode TEXT NOT NULL DEFAULT 'all',
|
|
rules TEXT NOT NULL DEFAULT '[]',
|
|
visibility TEXT NOT NULL DEFAULT 'shared',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
last_synced_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS host_group_members (
|
|
host_group_id TEXT NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE,
|
|
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (host_group_id, host_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS folders (
|
|
id TEXT PRIMARY KEY,
|
|
parent_id TEXT REFERENCES folders(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS scripts (
|
|
id TEXT PRIMARY KEY,
|
|
folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
content TEXT NOT NULL,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS asset_folders (
|
|
id TEXT PRIMARY KEY,
|
|
parent_id TEXT REFERENCES asset_folders(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS assets (
|
|
id TEXT PRIMARY KEY,
|
|
folder_id TEXT REFERENCES asset_folders(id) ON DELETE SET NULL,
|
|
name TEXT NOT NULL,
|
|
original_name TEXT NOT NULL,
|
|
description TEXT,
|
|
kind TEXT NOT NULL DEFAULT 'generic',
|
|
mime_type TEXT,
|
|
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
checksum TEXT NOT NULL,
|
|
storage_path TEXT NOT NULL,
|
|
package_path TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS asset_links (
|
|
id TEXT PRIMARY KEY,
|
|
asset_id TEXT NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
|
|
target_type TEXT NOT NULL,
|
|
target_id TEXT NOT NULL,
|
|
link_role TEXT NOT NULL DEFAULT 'reference',
|
|
package_path TEXT,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS custom_variables (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
category TEXT NOT NULL DEFAULT 'Custom',
|
|
value_type TEXT NOT NULL DEFAULT 'string',
|
|
secret_cipher TEXT NOT NULL,
|
|
secret_iv TEXT NOT NULL,
|
|
secret_tag TEXT NOT NULL,
|
|
sensitive INTEGER NOT NULL DEFAULT 0,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS psadt_profiles (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
app_vendor TEXT,
|
|
app_name TEXT NOT NULL,
|
|
app_version TEXT,
|
|
app_arch TEXT,
|
|
app_lang TEXT,
|
|
app_revision TEXT,
|
|
toolkit_version TEXT NOT NULL DEFAULT 'v4',
|
|
template_version TEXT NOT NULL DEFAULT 'v4',
|
|
deployment_type TEXT NOT NULL DEFAULT 'Install',
|
|
deploy_mode TEXT NOT NULL DEFAULT 'Interactive',
|
|
require_admin INTEGER NOT NULL DEFAULT 1,
|
|
zero_config INTEGER NOT NULL DEFAULT 0,
|
|
suppress_reboot INTEGER NOT NULL DEFAULT 0,
|
|
close_processes TEXT NOT NULL DEFAULT '[]',
|
|
install_tasks TEXT NOT NULL DEFAULT '[]',
|
|
ui_plan TEXT NOT NULL DEFAULT '{}',
|
|
config_plan TEXT NOT NULL DEFAULT '{}',
|
|
admx_plan TEXT NOT NULL DEFAULT '{}',
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS intune_deployments (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
profile_id TEXT REFERENCES psadt_profiles(id) ON DELETE SET NULL,
|
|
script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL,
|
|
source_folder TEXT,
|
|
intunewin_file TEXT,
|
|
app_type TEXT NOT NULL DEFAULT 'Windows app (Win32)',
|
|
command_style TEXT NOT NULL DEFAULT 'v4',
|
|
install_behavior TEXT NOT NULL DEFAULT 'system',
|
|
restart_behavior TEXT NOT NULL DEFAULT 'return-code',
|
|
ui_mode TEXT NOT NULL DEFAULT 'native-v4',
|
|
requirements TEXT NOT NULL DEFAULT '{}',
|
|
install_command TEXT NOT NULL,
|
|
uninstall_command TEXT NOT NULL,
|
|
detection_type TEXT NOT NULL DEFAULT 'custom-script',
|
|
detection_rule TEXT NOT NULL DEFAULT '',
|
|
return_codes TEXT NOT NULL DEFAULT '[]',
|
|
assignments TEXT NOT NULL DEFAULT '[]',
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
graph_app_id TEXT,
|
|
notes TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS graph_connections (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
tenant_id TEXT NOT NULL,
|
|
client_id TEXT NOT NULL,
|
|
secret_cipher TEXT NOT NULL,
|
|
secret_iv TEXT NOT NULL,
|
|
secret_tag TEXT NOT NULL,
|
|
cloud TEXT NOT NULL DEFAULT 'global',
|
|
graph_base_url TEXT NOT NULL DEFAULT 'https://graph.microsoft.com',
|
|
authority_host TEXT NOT NULL DEFAULT 'https://login.microsoftonline.com',
|
|
default_api_version TEXT NOT NULL DEFAULT 'v1.0',
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
last_test_status TEXT,
|
|
last_test_message TEXT,
|
|
last_test_at TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS intune_deployment_statuses (
|
|
id TEXT PRIMARY KEY,
|
|
deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
|
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
|
graph_app_id TEXT NOT NULL,
|
|
scope TEXT NOT NULL DEFAULT 'device',
|
|
principal_id TEXT,
|
|
principal_name TEXT,
|
|
device_id TEXT,
|
|
device_name TEXT,
|
|
user_id TEXT,
|
|
user_principal_name TEXT,
|
|
install_state TEXT,
|
|
install_state_detail TEXT,
|
|
error_code TEXT,
|
|
error_description TEXT,
|
|
last_sync_datetime TEXT,
|
|
raw_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS intune_deployment_sync_runs (
|
|
id TEXT PRIMARY KEY,
|
|
deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
|
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
|
graph_app_id TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
message TEXT,
|
|
summary TEXT NOT NULL DEFAULT '{}',
|
|
started_at TEXT NOT NULL,
|
|
ended_at TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS applications (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
vendor TEXT,
|
|
publisher TEXT,
|
|
current_version TEXT,
|
|
latest_known_version TEXT,
|
|
version_source TEXT NOT NULL DEFAULT 'manual',
|
|
version_source_ref TEXT,
|
|
graph_connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
|
current_graph_app_id TEXT,
|
|
auto_check INTEGER NOT NULL DEFAULT 0,
|
|
last_checked_at TEXT,
|
|
last_check_message TEXT,
|
|
notes TEXT,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS change_requests (
|
|
id TEXT PRIMARY KEY,
|
|
deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
|
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
|
action TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
requested_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
requester_email TEXT,
|
|
approver_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
approver_email TEXT,
|
|
reason TEXT,
|
|
decided_at TEXT,
|
|
executed_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS graph_audit_log (
|
|
id TEXT PRIMARY KEY,
|
|
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
|
deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE SET NULL,
|
|
actor_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
actor_email TEXT,
|
|
action TEXT NOT NULL,
|
|
target_graph_id TEXT,
|
|
status TEXT NOT NULL,
|
|
message TEXT,
|
|
request_json TEXT NOT NULL DEFAULT '{}',
|
|
response_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS script_versions (
|
|
id TEXT PRIMARY KEY,
|
|
script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
|
|
version INTEGER NOT NULL,
|
|
content TEXT NOT NULL,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS runplans (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
|
|
visibility TEXT NOT NULL DEFAULT 'personal',
|
|
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
|
parallel INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS runplan_hosts (
|
|
runplan_id TEXT NOT NULL REFERENCES runplans(id) ON DELETE CASCADE,
|
|
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (runplan_id, host_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS runplan_host_groups (
|
|
runplan_id TEXT NOT NULL REFERENCES runplans(id) ON DELETE CASCADE,
|
|
host_group_id TEXT NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (runplan_id, host_group_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS jobs (
|
|
id TEXT PRIMARY KEY,
|
|
runplan_id TEXT REFERENCES runplans(id) ON DELETE SET NULL,
|
|
script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL,
|
|
triggered_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at TEXT,
|
|
ended_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS job_hosts (
|
|
id TEXT PRIMARY KEY,
|
|
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL,
|
|
exit_code INTEGER,
|
|
started_at TEXT,
|
|
ended_at TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS job_logs (
|
|
id TEXT PRIMARY KEY,
|
|
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL,
|
|
stream TEXT NOT NULL,
|
|
line TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS system_job_actions (
|
|
id TEXT PRIMARY KEY,
|
|
job_id TEXT,
|
|
target_type TEXT NOT NULL,
|
|
target_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
message TEXT,
|
|
actor_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'db',
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
`);
|
|
|
|
ensureColumn('users', 'theme', "TEXT NOT NULL DEFAULT 'dashtreme'");
|
|
ensureColumn('users', 'job_title', 'TEXT');
|
|
ensureColumn('users', 'phone', 'TEXT');
|
|
ensureColumn('users', 'timezone', 'TEXT');
|
|
ensureColumn('users', 'avatar_url', 'TEXT');
|
|
ensureColumn('users', 'updated_at', 'TEXT');
|
|
ensureColumn('intune_deployments', 'source_folder', 'TEXT');
|
|
ensureColumn('intune_deployments', 'intunewin_file', 'TEXT');
|
|
ensureColumn('intune_deployments', 'command_style', "TEXT NOT NULL DEFAULT 'v4'");
|
|
ensureColumn('intune_deployments', 'install_behavior', "TEXT NOT NULL DEFAULT 'system'");
|
|
ensureColumn('intune_deployments', 'restart_behavior', "TEXT NOT NULL DEFAULT 'return-code'");
|
|
ensureColumn('intune_deployments', 'ui_mode', "TEXT NOT NULL DEFAULT 'native-v4'");
|
|
ensureColumn('intune_deployments', 'requirements', "TEXT NOT NULL DEFAULT '{}'");
|
|
ensureColumn('intune_deployments', 'graph_app_id', 'TEXT');
|
|
ensureColumn('intune_deployments', 'graph_connection_id', 'TEXT');
|
|
ensureColumn('intune_deployments', 'relationships', "TEXT NOT NULL DEFAULT '[]'");
|
|
ensureColumn('intune_deployments', 'application_id', 'TEXT REFERENCES applications(id) ON DELETE SET NULL');
|
|
// Auto-promote: after the soak window elapses, advance a rollout ring's intent.
|
|
ensureColumn('intune_deployments', 'auto_promote_after_hours', 'INTEGER NOT NULL DEFAULT 0');
|
|
ensureColumn('intune_deployments', 'auto_promote_ring', 'TEXT');
|
|
ensureColumn('intune_deployments', 'auto_promote_intent', "TEXT NOT NULL DEFAULT 'required'");
|
|
ensureColumn('intune_deployments', 'soak_started_at', 'TEXT');
|
|
ensureColumn('intune_deployments', 'promoted_at', 'TEXT');
|
|
// Host visibility scoping. Existing rows default to 'shared' so prior global
|
|
// hosts remain visible to everyone; new hosts can be personal/group-scoped.
|
|
ensureColumn('hosts', 'visibility', "TEXT NOT NULL DEFAULT 'shared'");
|
|
ensureColumn('hosts', 'owner_user_id', 'TEXT REFERENCES users(id) ON DELETE SET NULL');
|
|
ensureColumn('hosts', 'group_id', 'TEXT REFERENCES groups(id) ON DELETE SET NULL');
|
|
// Host provenance is hidden from normal manual edit forms but used by
|
|
// automation gates such as vCenter power-state checks before execution.
|
|
ensureColumn('hosts', 'source_type', "TEXT NOT NULL DEFAULT 'manual'");
|
|
ensureColumn('hosts', 'source_connection_id', 'TEXT');
|
|
ensureColumn('hosts', 'source_ref', 'TEXT');
|
|
// VMware connection type separates vCenter inventory/power-state APIs from
|
|
// standalone ESXi host checks that use the vSphere Web Services SOAP API.
|
|
ensureColumn('vcenter_connections', 'target_type', "TEXT NOT NULL DEFAULT 'vcenter'");
|
|
ensureColumn('vcenter_connections', 'credential_id', 'TEXT REFERENCES credentials(id) ON DELETE SET NULL');
|
|
ensureColumn('system_job_actions', 'metadata_json', "TEXT NOT NULL DEFAULT '{}'");
|
|
// Tenant write-back is opt-in per Graph connection. Default 0 so existing
|
|
// read-only connections can never be used to change a tenant by accident.
|
|
ensureColumn('graph_connections', 'allow_write', 'INTEGER NOT NULL DEFAULT 0');
|
|
// When set, tenant-changing actions through this connection require an
|
|
// approved change request before they run.
|
|
ensureColumn('graph_connections', 'require_approval', 'INTEGER NOT NULL DEFAULT 0');
|
|
// Optional named-RBAC lists (JSON arrays of user ids). Empty = no restriction
|
|
// beyond the allow_write/admin gates.
|
|
ensureColumn('graph_connections', 'publisher_ids', "TEXT NOT NULL DEFAULT '[]'");
|
|
ensureColumn('graph_connections', 'approver_ids', "TEXT NOT NULL DEFAULT '[]'");
|
|
}
|
|
|
|
export function seed() {
|
|
const userCount = db.prepare('SELECT COUNT(*) AS count FROM users').get().count;
|
|
if (userCount === 0) {
|
|
const adminId = id('usr');
|
|
db.prepare(`
|
|
INSERT INTO users (id, email, display_name, password_hash, auth_provider, role, created_at)
|
|
VALUES (?, ?, ?, ?, 'local', 'admin', ?)
|
|
`).run(
|
|
adminId,
|
|
config.defaultAdminEmail.toLowerCase(),
|
|
config.defaultAdminName,
|
|
bcrypt.hashSync(config.defaultAdminPassword, 12),
|
|
now()
|
|
);
|
|
const groupId = id('grp');
|
|
db.prepare('INSERT INTO groups (id, name, description, created_at) VALUES (?, ?, ?, ?)').run(
|
|
groupId,
|
|
'Operations',
|
|
'Default shared operations group',
|
|
now()
|
|
);
|
|
db.prepare('INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)').run(adminId, groupId);
|
|
|
|
const folderId = id('fld');
|
|
db.prepare(`
|
|
INSERT INTO folders (id, parent_id, name, visibility, owner_user_id, group_id, created_at, updated_at)
|
|
VALUES (?, NULL, 'Examples', 'shared', ?, NULL, ?, ?)
|
|
`).run(folderId, adminId, now(), now());
|
|
|
|
const scriptId = id('scr');
|
|
const sample = [
|
|
'Write-Output "POSHManager sample run"',
|
|
'Write-Output "Target host: $env:POSHM_HOST_NAME"',
|
|
'Get-Date'
|
|
].join('\n');
|
|
db.prepare(`
|
|
INSERT INTO scripts (id, folder_id, name, description, content, visibility, owner_user_id, group_id, version, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, 'shared', ?, NULL, 1, ?, ?)
|
|
`).run(scriptId, folderId, 'Sample Health Check.ps1', 'Simple smoke-test script for local execution.', sample, adminId, now(), now());
|
|
db.prepare(`
|
|
INSERT INTO script_versions (id, script_id, version, content, created_by, created_at)
|
|
VALUES (?, ?, 1, ?, ?, ?)
|
|
`).run(id('ver'), scriptId, sample, adminId, now());
|
|
}
|
|
|
|
// Env-pinned settings are refreshed from the environment on every boot and
|
|
// marked source='env' (locked in the UI). Unpinned settings are seeded once
|
|
// as editable defaults so admin edits in the Config screen survive restarts.
|
|
const pinned = db.prepare(`
|
|
INSERT INTO settings (key, value, source, updated_at)
|
|
VALUES (?, ?, 'env', ?)
|
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value, source = 'env', updated_at = excluded.updated_at
|
|
`);
|
|
const defaulted = db.prepare(`
|
|
INSERT INTO settings (key, value, source, updated_at)
|
|
VALUES (?, ?, 'default', ?)
|
|
ON CONFLICT(key) DO NOTHING
|
|
`);
|
|
for (const def of settingDefinitions()) {
|
|
if (def.pinned) pinned.run(def.key, def.value, now());
|
|
else defaulted.run(def.key, def.value, now());
|
|
}
|
|
}
|
|
|
|
export function visibleClause(alias = '') {
|
|
const p = alias ? `${alias}.` : '';
|
|
return `(${p}visibility = 'shared' OR ${p}owner_user_id = ? OR ${p}group_id IN (SELECT group_id FROM user_groups WHERE user_id = ?))`;
|
|
}
|