Sync Gitea releases to GitHub / sync-releases (push) Canceled after 0s
- Local accounts with bcrypt password hashing; first-run setup via POST /api/setup - Personal API tokens (dmv_<48hex>, SHA-256 hashed at rest) for scripted access - Server-side in-memory sessions with 32-byte secure cookie (dockmv_session, 7-day TTL, sliding renewal) - Login rate limiting (exponential backoff 1s–30s cap) per IP - Refuse to bind non-loopback while no account exists, unless DOCKMV_TRUST_ADDR=1 (for Docker's port mapping) - Every account can manage every other account (no roles in v1) - Auth middleware: public-path allowlist (/api/setup, /api/login, /api/logout, /api/me, /api/health) + session cookie check + API token (X-Auth-Token or Authorization: Bearer) check - Frontend AuthGate gates app on GET /api/me; shows setup screen or login form or app tree as needed - Account tab for personal token management; sign-out button in topbar - Break: removed --token flag, DOCKMV_TOKEN env var, ?token= query param, /api/health no longer auto-responds when unauthenticated Verified: go build/vet clean, frontend tsc+vite clean. Sandbox cannot execute binaries to test setup→login→session→token flow at runtime; recommend manual pass before merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
314 lines
5.9 KiB
TypeScript
314 lines
5.9 KiB
TypeScript
// Mirrors the Go types in internal/spec, internal/dkr and internal/job.
|
|
|
|
export type MountKind = 'volume' | 'anonymous' | 'bind' | 'tmpfs' | string
|
|
|
|
export interface Mount {
|
|
kind: MountKind
|
|
name?: string
|
|
source?: string
|
|
destination: string
|
|
readOnly: boolean
|
|
propagation?: string
|
|
tmpfsOpts?: string
|
|
sizeBytes: number
|
|
}
|
|
|
|
export interface Endpoint {
|
|
network: string
|
|
aliases?: string[]
|
|
ipv4Address?: string
|
|
ipv6Address?: string
|
|
macAddress?: string
|
|
}
|
|
|
|
export interface PortBinding {
|
|
containerPort: string
|
|
hostIp?: string
|
|
hostPort?: string
|
|
}
|
|
|
|
export interface Container {
|
|
id: string
|
|
name: string
|
|
state: string
|
|
image: string
|
|
imageId: string
|
|
imageDigest?: string
|
|
composeProject?: string
|
|
composeService?: string
|
|
env?: string[]
|
|
labels?: Record<string, string>
|
|
cmd?: string[]
|
|
entrypoint?: string[]
|
|
restartPolicy?: string
|
|
privileged?: boolean
|
|
networkMode?: string
|
|
endpoints?: Endpoint[]
|
|
ports?: PortBinding[]
|
|
mounts?: Mount[]
|
|
warnings?: string[]
|
|
}
|
|
|
|
export interface Volume {
|
|
name: string
|
|
driver: string
|
|
driverOpts?: Record<string, string>
|
|
labels?: Record<string, string>
|
|
}
|
|
|
|
export interface NetworkSpec {
|
|
name: string
|
|
driver: string
|
|
internal?: boolean
|
|
attachable?: boolean
|
|
}
|
|
|
|
export interface Inventory {
|
|
host: string
|
|
dockerVersion: string
|
|
containers: Container[]
|
|
volumes: Volume[]
|
|
networks: NetworkSpec[]
|
|
warnings?: string[]
|
|
}
|
|
|
|
export type ImageMode = 'auto' | 'pull' | 'stream' | 'skip'
|
|
export type MountAction = 'copy' | 'structure' | 'skip'
|
|
export type ConflictPolicy = 'fail' | 'skip' | 'replace' | 'rename'
|
|
|
|
export interface MountSelection {
|
|
action: MountAction
|
|
targetSource?: string
|
|
targetName?: string
|
|
}
|
|
|
|
export interface ItemSelection {
|
|
containerId: string
|
|
include: boolean
|
|
nameOverride?: string
|
|
migrateImage: boolean
|
|
imageMode: ImageMode
|
|
migrateNetworks: boolean
|
|
keepStaticIps: boolean
|
|
migratePorts: boolean
|
|
mounts: Record<string, MountSelection>
|
|
startAfter: boolean
|
|
stopSourceDuringCopy: boolean
|
|
stopSourceAfter: boolean
|
|
}
|
|
|
|
export interface Options {
|
|
conflict: ConflictPolicy
|
|
renameSuffix?: string
|
|
compress: boolean
|
|
compressLevel: number
|
|
dryRun: boolean
|
|
parallelism: number
|
|
verifyAfter: boolean
|
|
}
|
|
|
|
export interface Plan {
|
|
items: ItemSelection[]
|
|
options: Options
|
|
target?: string
|
|
packageName?: string
|
|
}
|
|
|
|
export interface SourceResponse {
|
|
inventory: Inventory
|
|
defaults: Record<string, ItemSelection>
|
|
options: Options
|
|
}
|
|
|
|
/** How a source daemon is reached. 'local' is the built-in, unremovable one. */
|
|
export type SourceKind = 'local' | 'docker' | 'ssh'
|
|
|
|
export interface Source {
|
|
id: string
|
|
name: string
|
|
kind: SourceKind
|
|
/** Daemon address for the 'docker' kind, e.g. tcp://10.0.0.5:2375. */
|
|
dockerHost?: string
|
|
/** Remote host for the 'ssh' kind; the same shape as a target connection. */
|
|
ssh?: Connection
|
|
}
|
|
|
|
export interface SourceStatus {
|
|
id: string
|
|
name: string
|
|
kind: SourceKind
|
|
endpoint: string
|
|
dockerVersion?: string
|
|
connected: boolean
|
|
error?: string
|
|
}
|
|
|
|
export interface SourcesResponse {
|
|
sources: Source[]
|
|
selected: string
|
|
current: SourceStatus | null
|
|
}
|
|
|
|
export type AuthMethod = 'password' | 'key' | 'agent'
|
|
|
|
export interface Connection {
|
|
id: string
|
|
name: string
|
|
host: string
|
|
port: number
|
|
user: string
|
|
auth: AuthMethod
|
|
password?: string
|
|
privateKey?: string
|
|
privateKeyPath?: string
|
|
passphrase?: string
|
|
sudo: boolean
|
|
dockerCmd?: string
|
|
saveSecrets: boolean
|
|
}
|
|
|
|
export interface Preflight {
|
|
dockerVersion: string
|
|
serverVersion: string
|
|
os: string
|
|
arch: string
|
|
hasGzip: boolean
|
|
diskFreeBytes: number
|
|
dockerRoot: string
|
|
problems?: string[]
|
|
}
|
|
|
|
export interface TargetContainer {
|
|
id: string
|
|
name: string
|
|
image: string
|
|
state: string
|
|
status: string
|
|
ports: string
|
|
}
|
|
|
|
export interface TargetInventory {
|
|
host: string
|
|
containers: TargetContainer[] | null
|
|
volumes: string[] | null
|
|
networks: string[] | null
|
|
preflight: Preflight
|
|
}
|
|
|
|
export interface HostKeyInfo {
|
|
host: string
|
|
keyType: string
|
|
fingerprint: string
|
|
trusted: boolean
|
|
changed: boolean
|
|
}
|
|
|
|
export type JobState = 'pending' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'canceled'
|
|
|
|
export interface Step {
|
|
id: string
|
|
label: string
|
|
state: JobState
|
|
bytesDone: number
|
|
bytesTotal: number
|
|
error?: string
|
|
startedAt?: string
|
|
endedAt?: string
|
|
}
|
|
|
|
export interface JobItem {
|
|
id: string
|
|
name: string
|
|
state: JobState
|
|
error?: string
|
|
steps: Step[]
|
|
warnings?: string[]
|
|
}
|
|
|
|
export interface LogEntry {
|
|
seq: number
|
|
at: string
|
|
level: 'info' | 'warn' | 'error' | 'cmd'
|
|
item?: string
|
|
message: string
|
|
}
|
|
|
|
export interface JobSnapshot {
|
|
id: string
|
|
kind: 'ssh' | 'package' | 'restore'
|
|
title: string
|
|
state: JobState
|
|
dryRun: boolean
|
|
error?: string
|
|
createdAt: string
|
|
startedAt?: string
|
|
endedAt?: string
|
|
items: JobItem[]
|
|
log: LogEntry[]
|
|
bytesDone: number
|
|
bytesTotal: number
|
|
artifact?: string
|
|
artifactBytes?: number
|
|
revision: number
|
|
}
|
|
|
|
export interface PreviewItem {
|
|
containerId: string
|
|
name: string
|
|
targetName: string
|
|
image: string
|
|
commands: string[] | null
|
|
transfers: string[] | null
|
|
notes: string[] | null
|
|
warnings: string[] | null
|
|
totalBytes: number
|
|
}
|
|
|
|
export interface PreviewResponse {
|
|
networkCommands: string[] | null
|
|
items: PreviewItem[]
|
|
}
|
|
|
|
export interface PackageInfo {
|
|
name: string
|
|
path: string
|
|
bytes: number
|
|
isDir: boolean
|
|
createdAt: string
|
|
}
|
|
|
|
export interface Health {
|
|
ok: boolean
|
|
dockerHost: string
|
|
dockerVersion?: string
|
|
dockerError?: string
|
|
source?: SourceStatus
|
|
packageDir: string
|
|
dataDir: string
|
|
knownHosts: string
|
|
}
|
|
|
|
export interface User {
|
|
id: string
|
|
username: string
|
|
createdAt: string
|
|
lastLoginAt?: string
|
|
}
|
|
|
|
export interface APIToken {
|
|
id: string
|
|
userId: string
|
|
name: string
|
|
hint: string
|
|
createdAt: string
|
|
lastUsedAt?: string
|
|
}
|
|
|
|
/** The response from GET /api/me, /api/setup and /api/login. */
|
|
export interface Me {
|
|
id?: string
|
|
username?: string
|
|
authenticated: boolean
|
|
needsSetup?: boolean
|
|
}
|