initial commit

This commit is contained in:
2025-11-23 11:29:12 +07:00
commit 382b57ed83
33 changed files with 2360 additions and 0 deletions

78
api/config-examples.md Normal file
View File

@@ -0,0 +1,78 @@
# Jagacloud Config Examples
## Agent config `/etc/jagacloud/agent.yaml`
```yaml
listen_addr: ":8000"
libvirt_uri: "qemu:///system"
lxc_path: "/etc/jagacloud/lxc"
podman_socket: "/run/podman/podman.sock"
auth_token: "replace-me"
storage_pools:
- name: local-dir
type: dir
path: /var/lib/jagacloud/images
- name: local-lvm
type: lvm
path: /dev/vg0
bridges:
- name: vmbr0
vlan_aware: true
mtu: 1500
```
## VM spec (persisted)
`/etc/jagacloud/vm/100.yaml`
```yaml
id: "100"
name: "vm-100"
cpus: 4
memory_mb: 8192
disks:
- name: root
pool: local-lvm
size_gb: 40
bus: virtio
nics:
- bridge: vmbr0
vlan: 10
model: virtio
cloud_init:
user: debian
ssh_keys:
- "ssh-ed25519 AAA... user@host"
```
## Container spec (persisted)
`/etc/jagacloud/ct/200.yaml`
```yaml
id: "200"
name: "ct-200"
unprivileged: true
limits:
cpus: 2
memory_mb: 2048
template: "debian-bookworm"
rootfs:
pool: local-dir
size_gb: 10
nics:
- bridge: vmbr0
vlan: 20
```
## Network bridge (systemd-networkd snippet)
`/etc/jagacloud/network/vmbr0.netdev`
```ini
[NetDev]
Name=vmbr0
Kind=bridge
```
`/etc/jagacloud/network/vmbr0.network`
```ini
[Match]
Name=vmbr0
[Network]
VLANFiltering=yes
```

219
api/openapi.yaml Normal file
View File

@@ -0,0 +1,219 @@
openapi: 3.0.3
info:
title: Jagacloud Node Agent API
version: 0.1.0
description: Minimal single-node hypervisor API for VMs (libvirt), LXC containers, and Podman-in-LXC.
servers:
- url: http://{host}:{port}
variables:
host:
default: 127.0.0.1
port:
default: "8000"
components:
securitySchemes:
bearerToken:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Error:
type: object
properties:
error:
type: string
NodeInfo:
type: object
properties:
hostname: { type: string }
version: { type: string }
cpu: { type: object, properties: { cores: {type: integer}, model: {type: string}, load: {type: number} } }
memory: { type: object, properties: { total_mb: {type: integer}, used_mb: {type: integer} } }
storage_pools:
type: array
items: { $ref: '#/components/schemas/StoragePool' }
bridges:
type: array
items: { $ref: '#/components/schemas/Bridge' }
StoragePool:
type: object
properties:
name: { type: string }
type: { type: string, enum: [dir, lvm, zfs] }
path: { type: string }
free_gb: { type: number }
total_gb: { type: number }
Bridge:
type: object
properties:
name: { type: string }
vlan_aware: { type: boolean }
mtu: { type: integer }
VM:
type: object
properties:
id: { type: string }
name: { type: string }
status: { type: string, enum: [running, stopped, paused, error] }
cpus: { type: integer }
memory_mb: { type: integer }
disks: { type: array, items: { $ref: '#/components/schemas/VMDisk' } }
nics: { type: array, items: { $ref: '#/components/schemas/VMNic' } }
VMDisk:
type: object
properties:
name: { type: string }
pool: { type: string }
size_gb: { type: integer }
bus: { type: string, enum: [virtio, sata] }
VMNic:
type: object
properties:
bridge: { type: string }
model: { type: string }
vlan: { type: integer }
VMCreate:
allOf:
- $ref: '#/components/schemas/VM'
- type: object
required: [name, cpus, memory_mb]
properties:
cloud_init:
type: object
properties:
user: { type: string }
ssh_keys: { type: array, items: {type: string} }
user_data: { type: string }
Container:
type: object
properties:
id: { type: string }
name: { type: string }
status: { type: string, enum: [running, stopped, error] }
unprivileged: { type: boolean }
nics: { type: array, items: { $ref: '#/components/schemas/ContainerNic' } }
limits: { type: object, properties: { cpus: {type: integer}, memory_mb: {type: integer} } }
ContainerCreate:
allOf:
- $ref: '#/components/schemas/Container'
- type: object
required: [name, template]
properties:
template: { type: string }
rootfs: { type: object, properties: { pool: {type: string}, size_gb: {type: integer} }, required: [pool, size_gb] }
ContainerNic:
type: object
properties:
bridge: { type: string }
vlan: { type: integer }
OCIContainer:
type: object
properties:
id: { type: string }
image: { type: string }
status: { type: string }
OCICreate:
type: object
required: [image]
properties:
image: { type: string }
cmd: { type: array, items: {type: string} }
env: { type: object, additionalProperties: {type: string} }
ports: { type: array, items: { type: object, properties: { host_port: {type: integer}, container_port: {type: integer} } } }
volumes: { type: array, items: { type: string } }
restart: { type: string }
security:
- bearerToken: []
paths:
/api/v1/node:
get:
summary: Node information
responses:
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/NodeInfo' } } } }
default: { description: Error, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
/api/v1/vms:
get:
summary: List VMs
responses:
'200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/VM' } } } } }
post:
summary: Create VM
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/VMCreate' } } }
responses:
'202': { description: Accepted, content: { application/json: { schema: { $ref: '#/components/schemas/VM' } } } }
/api/v1/vms/{id}:
get:
summary: Get VM
parameters: [{ name: id, in: path, required: true, schema: {type: string} }]
responses:
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/VM' } } } }
post:
summary: Update VM (reserved for future)
responses:
'501': { description: Not implemented }
/api/v1/vms/{id}/{action}:
post:
summary: VM lifecycle action
parameters:
- { name: id, in: path, required: true, schema: {type: string} }
- { name: action, in: path, required: true, schema: { type: string, enum: [start, stop, reboot, delete] } }
responses:
'202': { description: Accepted }
/api/v1/containers:
get:
summary: List LXC containers
responses:
'200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Container' } } } } }
post:
summary: Create container
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/ContainerCreate' } } }
responses:
'202': { description: Accepted, content: { application/json: { schema: { $ref: '#/components/schemas/Container' } } } }
/api/v1/containers/{id}:
get:
summary: Get container
parameters: [{ name: id, in: path, required: true, schema: {type: string} }]
responses:
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Container' } } } }
/api/v1/containers/{id}/{action}:
post:
summary: Container lifecycle action
parameters:
- { name: id, in: path, required: true, schema: {type: string} }
- { name: action, in: path, required: true, schema: { type: string, enum: [start, stop, delete] } }
responses:
'202': { description: Accepted }
/api/v1/containers/{id}/oci:
get:
summary: List OCI containers inside CT
parameters: [{ name: id, in: path, required: true, schema: {type: string} }]
responses:
'200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/OCIContainer' } } } } }
post:
summary: Create OCI container inside CT
requestBody:
required: true
content: { application/json: { schema: { $ref: '#/components/schemas/OCICreate' } } }
responses:
'202': { description: Accepted, content: { application/json: { schema: { $ref: '#/components/schemas/OCIContainer' } } } }
/api/v1/containers/{id}/oci/{cid}:
get:
summary: Get OCI container
parameters:
- { name: id, in: path, required: true, schema: {type: string} }
- { name: cid, in: path, required: true, schema: {type: string} }
responses:
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/OCIContainer' } } } }
/api/v1/containers/{id}/oci/{cid}/{action}:
post:
summary: OCI lifecycle action
parameters:
- { name: id, in: path, required: true, schema: {type: string} }
- { name: cid, in: path, required: true, schema: {type: string} }
- { name: action, in: path, required: true, schema: { type: string, enum: [start, stop, delete] } }
responses:
'202': { description: Accepted }