feat: Fleet Forge UEM - device management, agent, remote desktop (VNC), TLS reverse proxy
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
auto_https off
|
||||
admin off
|
||||
}
|
||||
|
||||
http://10.0.0.194 {
|
||||
redir https://10.0.0.194{uri} permanent
|
||||
}
|
||||
|
||||
http://localhost {
|
||||
redir https://10.0.0.194{uri} permanent
|
||||
}
|
||||
|
||||
https://10.0.0.194 {
|
||||
tls /certs/fleetforge.pem /certs/fleetforge-key.pem
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy 127.0.0.1:3002
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:3005
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /certs/fleetforge.pem;
|
||||
ssl_certificate_key /certs/fleetforge-key.pem;
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
proxy_send_timeout 86400;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3005;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
FROM node:20-alpine AS base
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./
|
||||
COPY apps/api/package.json ./apps/api/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
RUN pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
|
||||
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||
COPY . .
|
||||
RUN pnpm --filter @fleetforge/shared build
|
||||
RUN pnpm --filter @fleetforge/api build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 fleetforge
|
||||
COPY --from=builder /app/apps/api/dist ./dist
|
||||
COPY --from=builder /app/apps/api/package.json ./
|
||||
COPY --from=builder /app/packages/shared/dist ../packages/shared/dist
|
||||
COPY --from=builder /app/packages/shared/package.json ../packages/shared/
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
USER fleetforge
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -0,0 +1,30 @@
|
||||
FROM node:20-alpine AS base
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
COPY packages/ui/package.json ./packages/ui/
|
||||
RUN pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
|
||||
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||
COPY --from=deps /app/packages/ui/node_modules ./packages/ui/node_modules
|
||||
COPY . .
|
||||
RUN pnpm --filter @fleetforge/shared build
|
||||
RUN pnpm --filter @fleetforge/web build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 fleetforge
|
||||
COPY --from=builder /app/apps/web/.next/standalone ./
|
||||
COPY --from=builder /app/apps/web/.next/static ./.next/static
|
||||
COPY --from=builder /app/apps/web/public ./public
|
||||
USER fleetforge
|
||||
EXPOSE 3001
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,26 @@
|
||||
FROM node:20-alpine AS base
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./
|
||||
COPY apps/worker/package.json ./apps/worker/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
RUN pnpm install --frozen-lockfile || pnpm install
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/apps/worker/node_modules ./apps/worker/node_modules
|
||||
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||
COPY . .
|
||||
RUN pnpm --filter @fleetforge/shared build
|
||||
RUN pnpm --filter @fleetforge/worker build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 fleetforge
|
||||
COPY --from=builder /app/apps/worker/dist ./dist
|
||||
COPY --from=builder /app/apps/worker/package.json ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
USER fleetforge
|
||||
CMD ["node", "dist/index.js"]
|
||||
@@ -0,0 +1,82 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: fleetforge
|
||||
POSTGRES_PASSWORD: fleetforge
|
||||
POSTGRES_DB: fleetforge
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fleetforge"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: infrastructure/docker/Dockerfile.api
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
DATABASE_URL: postgresql://fleetforge:fleetforge@postgres:5432/fleetforge?schema=public
|
||||
REDIS_URL: redis://redis:6379
|
||||
JWT_SECRET: dev-jwt-secret-do-not-use-in-production
|
||||
SESSION_SECRET: dev-session-secret-do-not-use-in-production
|
||||
NODE_ENV: development
|
||||
LOG_LEVEL: debug
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ../../apps/api:/app
|
||||
- /app/node_modules
|
||||
- ../../infrastructure/migrations:/app/infrastructure/migrations
|
||||
- ../../docs:/app/docs
|
||||
|
||||
worker:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: infrastructure/docker/Dockerfile.worker
|
||||
environment:
|
||||
DATABASE_URL: postgresql://fleetforge:fleetforge@postgres:5432/fleetforge?schema=public
|
||||
NODE_ENV: development
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ../../apps/worker:/app
|
||||
- /app/node_modules
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: infrastructure/docker/Dockerfile.web
|
||||
ports:
|
||||
- "3001:3001"
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: http://localhost:3000/api
|
||||
depends_on:
|
||||
- api
|
||||
volumes:
|
||||
- ../../apps/web:/app
|
||||
- /app/node_modules
|
||||
- /app/.next
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends msitools wixl wixl-data && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*"
|
||||
Name="Fleet Forge Agent"
|
||||
Language="1033"
|
||||
Version="__VERSION__"
|
||||
Manufacturer="FleetForge"
|
||||
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890">
|
||||
|
||||
<Package InstallerVersion="200"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
Description="Fleet Forge Agent"
|
||||
Comments="Fleet Forge endpoint management agent for Windows" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Feature Id="AgentFeature" Title="Fleet Forge Agent" Level="1">
|
||||
<ComponentRef Id="AgentBinary" />
|
||||
<ComponentRef Id="AgentConfig" />
|
||||
</Feature>
|
||||
|
||||
<Icon Id="FleetForgeIcon" SourceFile="$(var.AgentBinaryPath)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="FleetForgeIcon" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFilesFolder">
|
||||
<Directory Id="INSTALLFOLDER" Name="FleetForge">
|
||||
<Component Id="AgentBinary" Guid="B1C2D3E4-F5A6-7890-BCDE-F12345678901" Win64="yes">
|
||||
<File Id="AgentExe"
|
||||
Source="$(var.AgentBinaryPath)"
|
||||
KeyPath="yes"
|
||||
Name="forge-agent.exe" />
|
||||
<ServiceInstall Id="FleetForgeService"
|
||||
Name="FleetForgeAgent"
|
||||
DisplayName="Fleet Forge Agent"
|
||||
Description="Fleet Forge endpoint management agent"
|
||||
Start="auto"
|
||||
Type="ownProcess"
|
||||
ErrorControl="normal"
|
||||
Interactive="no"
|
||||
Account="NT AUTHORITY\SYSTEM"
|
||||
Arguments="run --service" />
|
||||
<ServiceControl Id="FleetForgeServiceControl"
|
||||
Name="FleetForgeAgent"
|
||||
Stop="both"
|
||||
Remove="uninstall"
|
||||
Wait="no" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Directory Id="CommonAppDataFolder">
|
||||
<Directory Id="FleetForgeDataFolder" Name="FleetForge">
|
||||
<Component Id="AgentConfig" Guid="C2D3E4F5-A6B7-8901-CDEF-123456789012" Win64="yes">
|
||||
<CreateFolder />
|
||||
<File Id="ConfigJson"
|
||||
Source="$(var.ConfigFilePath)"
|
||||
KeyPath="yes"
|
||||
Name="config.json" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOCKER_IMAGE="fleetforge-wixl"
|
||||
|
||||
AGENT_BIN="${1:?Usage: $0 <agent-binary> <config-json> <version> <output.msi>}"
|
||||
CONFIG_JSON="${2:?Usage: $0 <agent-binary> <config-json> <version> <output.msi>}"
|
||||
VERSION="${3:?Usage: $0 <agent-binary> <config-json> <version> <output.msi>}"
|
||||
OUTPUT="${4:?Usage: $0 <agent-binary> <config-json> <version> <output.msi>}"
|
||||
|
||||
if [ ! -f "$AGENT_BIN" ]; then
|
||||
echo "Error: Agent binary not found: $AGENT_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$CONFIG_JSON" ]; then
|
||||
echo "Error: Config file not found: $CONFIG_JSON" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker image inspect "$DOCKER_IMAGE" >/dev/null 2>&1; then
|
||||
echo "Building wixl Docker image..."
|
||||
docker build -t "$DOCKER_IMAGE" "$SCRIPT_DIR"
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
cp "$AGENT_BIN" "$TMPDIR/forge-agent.exe"
|
||||
cp "$CONFIG_JSON" "$TMPDIR/config.json"
|
||||
|
||||
sed "s/__VERSION__/$VERSION/g" "$SCRIPT_DIR/agent.wxs" > "$TMPDIR/agent.wxs"
|
||||
|
||||
echo "Compiling MSI with wixl..."
|
||||
docker run --rm \
|
||||
-v "$TMPDIR:/work" \
|
||||
"$DOCKER_IMAGE" \
|
||||
wixl -o /work/output.msi \
|
||||
-D "AgentBinaryPath=/work/forge-agent.exe" \
|
||||
-D "ConfigFilePath=/work/config.json" \
|
||||
/work/agent.wxs
|
||||
|
||||
cp "$TMPDIR/output.msi" "$OUTPUT"
|
||||
echo "MSI generated: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"
|
||||
@@ -0,0 +1,234 @@
|
||||
CREATE TABLE IF NOT EXISTS "organizations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"name" varchar(255) NOT NULL,
|
||||
"slug" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "organizations_slug_unique" UNIQUE ("slug")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"password_hash" varchar(255) NOT NULL,
|
||||
"display_name" varchar(255) NOT NULL,
|
||||
"role" varchar(50) DEFAULT 'read_only' NOT NULL,
|
||||
"is_active" boolean DEFAULT true NOT NULL,
|
||||
"last_login_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_org_unique" UNIQUE ("email","organization_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "enrollment_tokens" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"token_hash" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"max_uses" integer,
|
||||
"use_count" integer DEFAULT 0 NOT NULL,
|
||||
"is_revoked" boolean DEFAULT false NOT NULL,
|
||||
"label" varchar(255),
|
||||
"created_by_user_id" uuid NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "enrollment_tokens_token_hash_unique" UNIQUE ("token_hash")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "device_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "device_groups_name_org_unique" UNIQUE ("name","organization_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "devices" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"device_group_id" uuid,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"device_type" varchar(50) NOT NULL,
|
||||
"platform" varchar(50) NOT NULL,
|
||||
"os_name" varchar(100) NOT NULL,
|
||||
"os_version" varchar(100) NOT NULL,
|
||||
"architecture" varchar(50) NOT NULL,
|
||||
"kernel_version" varchar(100),
|
||||
"cpu_model" varchar(255),
|
||||
"cpu_cores" integer,
|
||||
"total_memory_bytes" bigint,
|
||||
"total_disk_bytes" bigint,
|
||||
"ip_address" varchar(45),
|
||||
"agent_version" varchar(50) NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'offline' NOT NULL,
|
||||
"last_heartbeat_at" timestamp with time zone,
|
||||
"enrolled_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"is_disabled" boolean DEFAULT false NOT NULL,
|
||||
"is_archived" boolean DEFAULT false NOT NULL,
|
||||
"tags" jsonb DEFAULT '[]' NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "device_credentials" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"device_id" uuid NOT NULL,
|
||||
"credential_type" varchar(50) NOT NULL,
|
||||
"credential_hash" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "inventory_snapshots" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"device_id" uuid NOT NULL,
|
||||
"reported_at" timestamp with time zone NOT NULL,
|
||||
"packages" jsonb DEFAULT '[]' NOT NULL,
|
||||
"services" jsonb DEFAULT '[]' NOT NULL,
|
||||
"network_interfaces" jsonb DEFAULT '[]' NOT NULL,
|
||||
"platform_data" jsonb DEFAULT '{}' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "metric_snapshots" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"device_id" uuid NOT NULL,
|
||||
"reported_at" timestamp with time zone NOT NULL,
|
||||
"cpu_usage_percent" integer,
|
||||
"memory_usage_percent" integer,
|
||||
"memory_used_bytes" bigint,
|
||||
"memory_total_bytes" bigint,
|
||||
"disk_usage_percent" integer,
|
||||
"disk_used_bytes" bigint,
|
||||
"disk_total_bytes" bigint,
|
||||
"load_average_1m" integer,
|
||||
"load_average_5m" integer,
|
||||
"load_average_15m" integer,
|
||||
"uptime_seconds" bigint,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "policies" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"rules" jsonb DEFAULT '[]' NOT NULL,
|
||||
"is_enabled" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "compliance_results" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"device_id" uuid NOT NULL,
|
||||
"policy_id" uuid NOT NULL,
|
||||
"status" varchar(50) NOT NULL,
|
||||
"details" jsonb DEFAULT '{}' NOT NULL,
|
||||
"evaluated_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "jobs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"created_by_user_id" uuid NOT NULL,
|
||||
"job_type" varchar(100) NOT NULL,
|
||||
"status" varchar(50) DEFAULT 'queued' NOT NULL,
|
||||
"parameters" jsonb DEFAULT '{}' NOT NULL,
|
||||
"dispatched_at" timestamp with time zone,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"expires_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "job_targets" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"job_id" uuid NOT NULL,
|
||||
"device_id" uuid NOT NULL,
|
||||
"status" varchar(50) DEFAULT 'queued' NOT NULL,
|
||||
"result" jsonb,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "alerts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"device_id" uuid,
|
||||
"alert_type" varchar(100) NOT NULL,
|
||||
"severity" varchar(20) NOT NULL,
|
||||
"title" varchar(255) NOT NULL,
|
||||
"message" text NOT NULL,
|
||||
"is_resolved" boolean DEFAULT false NOT NULL,
|
||||
"resolved_at" timestamp with time zone,
|
||||
"resolved_by_user_id" uuid,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "audit_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"user_id" uuid,
|
||||
"action" varchar(255) NOT NULL,
|
||||
"target_type" varchar(100) NOT NULL,
|
||||
"target_id" uuid,
|
||||
"device_ids" jsonb DEFAULT '[]' NOT NULL,
|
||||
"result" varchar(50) NOT NULL,
|
||||
"ip_address" varchar(45),
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"correlation_id" varchar(255) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "api_tokens" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" uuid NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"token_hash" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp with time zone,
|
||||
"last_used_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "api_tokens_token_hash_unique" UNIQUE ("token_hash")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "idx_users_email_org" ON "users" ("email","organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_users_organization_id" ON "users" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_enrollment_tokens_org" ON "enrollment_tokens" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_enrollment_tokens_hash" ON "enrollment_tokens" ("token_hash");
|
||||
CREATE INDEX IF NOT EXISTS "idx_devices_org" ON "devices" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_devices_status" ON "devices" ("status");
|
||||
CREATE INDEX IF NOT EXISTS "idx_devices_group" ON "devices" ("device_group_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_devices_type" ON "devices" ("device_type");
|
||||
CREATE INDEX IF NOT EXISTS "idx_devices_last_heartbeat" ON "devices" ("last_heartbeat_at");
|
||||
CREATE INDEX IF NOT EXISTS "idx_device_credentials_device" ON "device_credentials" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_inventory_device" ON "inventory_snapshots" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_inventory_reported_at" ON "inventory_snapshots" ("reported_at");
|
||||
CREATE INDEX IF NOT EXISTS "idx_metrics_device" ON "metric_snapshots" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_metrics_reported_at" ON "metric_snapshots" ("reported_at");
|
||||
CREATE INDEX IF NOT EXISTS "idx_policies_org" ON "policies" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_compliance_device" ON "compliance_results" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_compliance_policy" ON "compliance_results" ("policy_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_jobs_org" ON "jobs" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_jobs_status" ON "jobs" ("status");
|
||||
CREATE INDEX IF NOT EXISTS "idx_jobs_type" ON "jobs" ("job_type");
|
||||
CREATE INDEX IF NOT EXISTS "idx_job_targets_job" ON "job_targets" ("job_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_job_targets_device" ON "job_targets" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_job_targets_status" ON "job_targets" ("status");
|
||||
CREATE INDEX IF NOT EXISTS "idx_alerts_org" ON "alerts" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_alerts_device" ON "alerts" ("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_alerts_resolved" ON "alerts" ("is_resolved");
|
||||
CREATE INDEX IF NOT EXISTS "idx_audit_org" ON "audit_events" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_audit_user" ON "audit_events" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_audit_action" ON "audit_events" ("action");
|
||||
CREATE INDEX IF NOT EXISTS "idx_audit_created_at" ON "audit_events" ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "idx_audit_correlation" ON "audit_events" ("correlation_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_api_tokens_org" ON "api_tokens" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_api_tokens_hash" ON "api_tokens" ("token_hash");
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add unique constraint on (device_id, policy_id) for compliance_results upsert
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "idx_compliance_device_policy"
|
||||
ON "compliance_results" ("device_id", "policy_id");
|
||||
@@ -0,0 +1,68 @@
|
||||
-- Update Requests: approval workflow for software updates
|
||||
CREATE TABLE IF NOT EXISTS "update_requests" (
|
||||
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" UUID NOT NULL REFERENCES "organizations"("id"),
|
||||
"device_id" UUID NOT NULL REFERENCES "devices"("id") ON DELETE CASCADE,
|
||||
"package_name" VARCHAR(255) NOT NULL,
|
||||
"package_manager" VARCHAR(50) NOT NULL DEFAULT 'apt',
|
||||
"current_version" VARCHAR(100),
|
||||
"target_version" VARCHAR(100) NOT NULL,
|
||||
"requested_by_user_id" UUID NOT NULL REFERENCES "users"("id"),
|
||||
"status" VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
"approved_by_user_id" UUID REFERENCES "users"("id"),
|
||||
"approved_at" TIMESTAMPTZ,
|
||||
"rejection_reason" TEXT,
|
||||
"deployment_id" UUID,
|
||||
"notes" TEXT,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_update_requests_org" ON "update_requests"("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_update_requests_device" ON "update_requests"("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_update_requests_status" ON "update_requests"("status");
|
||||
|
||||
-- Deployments: track bulk operations across devices
|
||||
CREATE TABLE IF NOT EXISTS "deployments" (
|
||||
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"organization_id" UUID NOT NULL REFERENCES "organizations"("id"),
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"description" TEXT,
|
||||
"job_type" VARCHAR(100) NOT NULL,
|
||||
"parameters" JSONB NOT NULL DEFAULT '{}',
|
||||
"status" VARCHAR(50) NOT NULL DEFAULT 'draft',
|
||||
"created_by_user_id" UUID NOT NULL REFERENCES "users"("id"),
|
||||
"approved_by_user_id" UUID REFERENCES "users"("id"),
|
||||
"approved_at" TIMESTAMPTZ,
|
||||
"scheduled_at" TIMESTAMPTZ,
|
||||
"started_at" TIMESTAMPTZ,
|
||||
"completed_at" TIMESTAMPTZ,
|
||||
"total_targets" INTEGER NOT NULL DEFAULT 0,
|
||||
"completed_targets" INTEGER NOT NULL DEFAULT 0,
|
||||
"failed_targets" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_deployments_org" ON "deployments"("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_deployments_status" ON "deployments"("status");
|
||||
|
||||
-- Deployment Targets: per-device tracking within a deployment
|
||||
CREATE TABLE IF NOT EXISTS "deployment_targets" (
|
||||
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"deployment_id" UUID NOT NULL REFERENCES "deployments"("id") ON DELETE CASCADE,
|
||||
"device_id" UUID NOT NULL REFERENCES "devices"("id") ON DELETE CASCADE,
|
||||
"job_id" UUID REFERENCES "jobs"("id"),
|
||||
"status" VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
"result" JSONB,
|
||||
"started_at" TIMESTAMPTZ,
|
||||
"completed_at" TIMESTAMPTZ,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_deployment_targets_deployment" ON "deployment_targets"("deployment_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_deployment_targets_device" ON "deployment_targets"("device_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_deployment_targets_status" ON "deployment_targets"("status");
|
||||
|
||||
-- Link update_requests.deployment_id foreign key
|
||||
ALTER TABLE "update_requests"
|
||||
ADD CONSTRAINT "fk_update_requests_deployment"
|
||||
FOREIGN KEY ("deployment_id") REFERENCES "deployments"("id")
|
||||
ON DELETE SET NULL;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Convert metric snapshot columns from integer to real for float values from agents
|
||||
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN cpu_usage_percent TYPE real;
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN memory_usage_percent TYPE real;
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN disk_usage_percent TYPE real;
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN load_average_1m TYPE real;
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN load_average_5m TYPE real;
|
||||
ALTER TABLE metric_snapshots ALTER COLUMN load_average_15m TYPE real;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Software packages table for uploaded installers
|
||||
CREATE TABLE software_packages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
version VARCHAR(100) NOT NULL,
|
||||
package_type VARCHAR(50) NOT NULL,
|
||||
platform VARCHAR(50) NOT NULL,
|
||||
architecture VARCHAR(50) NOT NULL,
|
||||
filename VARCHAR(500) NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
checksum VARCHAR(255),
|
||||
install_command TEXT,
|
||||
tags JSONB NOT NULL DEFAULT '[]',
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
uploaded_by_user_id UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_software_packages_org ON software_packages(organization_id);
|
||||
CREATE INDEX idx_software_packages_platform ON software_packages(platform);
|
||||
CREATE INDEX idx_software_packages_name ON software_packages(name);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Remote desktop session table for VNC tunneled sessions
|
||||
CREATE TABLE remote_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id),
|
||||
device_id UUID NOT NULL REFERENCES devices(id),
|
||||
created_by_user_id UUID NOT NULL REFERENCES users(id),
|
||||
job_id UUID REFERENCES jobs(id),
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
vnc_port INTEGER NOT NULL DEFAULT 5900,
|
||||
vnc_password VARCHAR(64),
|
||||
error_message TEXT,
|
||||
started_at TIMESTAMP WITH TIME ZONE,
|
||||
ended_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_remote_sessions_org ON remote_sessions(organization_id);
|
||||
CREATE INDEX idx_remote_sessions_device ON remote_sessions(device_id);
|
||||
CREATE INDEX idx_remote_sessions_status ON remote_sessions(status);
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Allow jobs referenced by a remote session to be deleted/reaped:
|
||||
-- orphaned-session references are cleared instead of blocking the job removal.
|
||||
ALTER TABLE remote_sessions
|
||||
DROP CONSTRAINT remote_sessions_job_id_fkey,
|
||||
ADD CONSTRAINT remote_sessions_job_id_fkey
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
ON DELETE SET NULL;
|
||||
@@ -0,0 +1,36 @@
|
||||
[Unit]
|
||||
Description=Fleet Forge Agent - Endpoint Management Agent
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
ExecStart=/usr/local/bin/forge-agent run
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
|
||||
Environment=FORGE_API_URL=http://localhost:3000
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=forge-agent
|
||||
|
||||
NoNewPrivileges=false
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/etc/forge-agent /var/lib/forge-agent
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ProtectControlGroups=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictNamespaces=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user