feat: initial SleepGuard implementation

Wake-on-demand proxy + agent system with SvelteKit dashboard.
Monorepo: shared types, proxy (Hono + http-proxy), agent (monitors + locks), web (SvelteKit SPA).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vadim Sobinin
2026-02-10 13:46:51 +03:00
commit 852e01df39
64 changed files with 4864 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
export * from './types/common.js';
export * from './types/agent.js';
export * from './types/proxy.js';
export * from './types/upsnap.js';
export * from './utils/env.js';

View File

@@ -0,0 +1,60 @@
export interface CpuStatus {
usagePercent: number;
loadAvg: [number, number, number];
cores: number;
}
export interface GpuStatus {
available: boolean;
usagePercent: number;
memoryUsedMB: number;
memoryTotalMB: number;
temperature: number;
name: string;
}
export interface MemoryStatus {
usedPercent: number;
usedMB: number;
totalMB: number;
}
export interface DiskIoStatus {
active: boolean;
readKBps: number;
writeKBps: number;
}
export interface ProcessInfo {
name: string;
running: boolean;
pids: number[];
}
export interface Lock {
name: string;
reason?: string;
createdAt: string;
expiresAt?: string;
}
export interface CreateLockRequest {
name: string;
ttlSeconds?: number;
reason?: string;
}
export interface AgentStatus {
cpu: CpuStatus;
gpu: GpuStatus;
memory: MemoryStatus;
diskIo: DiskIoStatus;
processes: ProcessInfo[];
locks: Lock[];
uptime: number;
}
export interface CanShutdownResponse {
canShutdown: boolean;
reasons: string[];
}

View File

@@ -0,0 +1,20 @@
export enum MachineState {
OFFLINE = 'OFFLINE',
WAKING = 'WAKING',
ONLINE = 'ONLINE',
IDLE_CHECK = 'IDLE_CHECK',
SHUTTING_DOWN = 'SHUTTING_DOWN',
}
export interface ServiceConfig {
name: string;
host: string;
target: string;
}
export interface LogEntry {
timestamp: string;
level: 'info' | 'warn' | 'error';
message: string;
details?: string;
}

View File

@@ -0,0 +1,41 @@
import type { MachineState, ServiceConfig, LogEntry } from './common.js';
import type { AgentStatus, Lock } from './agent.js';
export interface ProxyConfig {
idleTimeoutMinutes: number;
healthCheckIntervalSeconds: number;
wakingTimeoutSeconds: number;
}
export interface ServiceHealth {
name: string;
host: string;
target: string;
healthy: boolean;
lastCheck: string | null;
}
export interface ProxyStatus {
state: MachineState;
services: ServiceHealth[];
config: ProxyConfig;
idleTimer: {
lastActivity: string;
remainingSeconds: number;
} | null;
agent: AgentStatus | null;
locks: Lock[];
logs: LogEntry[];
}
export type WsMessageType =
| 'state_change'
| 'status_update'
| 'service_health'
| 'log';
export interface WsMessage {
type: WsMessageType;
data: unknown;
timestamp: string;
}

View File

@@ -0,0 +1,18 @@
export interface UpSnapAuthResponse {
token: string;
record: {
id: string;
email: string;
username: string;
};
}
export interface UpSnapDevice {
id: string;
name: string;
ip: string;
mac: string;
status: string;
collectionId: string;
collectionName: string;
}

View File

@@ -0,0 +1,27 @@
export function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
export function optionalEnv(name: string, defaultValue: string): string {
return process.env[name] ?? defaultValue;
}
export function intEnv(name: string, defaultValue: number): number {
const raw = process.env[name];
if (!raw) return defaultValue;
const parsed = parseInt(raw, 10);
if (isNaN(parsed)) {
throw new Error(`Environment variable ${name} must be a number, got: ${raw}`);
}
return parsed;
}
export function boolEnv(name: string, defaultValue: boolean): boolean {
const raw = process.env[name];
if (!raw) return defaultValue;
return raw === 'true' || raw === '1';
}