diff --git a/cmd/sylve/main.go b/cmd/sylve/main.go index 861e0a7f..106317d8 100644 --- a/cmd/sylve/main.go +++ b/cmd/sylve/main.go @@ -15,7 +15,6 @@ import ( "net/http" "os" "os/signal" - "strings" "sync" "syscall" "time" @@ -35,13 +34,14 @@ import ( "github.com/alchemillahq/sylve/internal/services/jail" "github.com/alchemillahq/sylve/internal/services/libvirt" "github.com/alchemillahq/sylve/internal/services/lifecycle" - "github.com/alchemillahq/sylve/internal/services/network" + networkService "github.com/alchemillahq/sylve/internal/services/network" "github.com/alchemillahq/sylve/internal/services/samba" "github.com/alchemillahq/sylve/internal/services/system" "github.com/alchemillahq/sylve/internal/services/utilities" "github.com/alchemillahq/sylve/internal/services/zelta" "github.com/alchemillahq/sylve/internal/services/zfs" + portnetwork "github.com/alchemillahq/sylve/pkg/network" sysU "github.com/alchemillahq/sylve/pkg/system" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" @@ -58,6 +58,9 @@ func main() { cfg := config.ParseConfig(cfgPath) logger.InitLogger(cfg.DataPath, cfg.LogLevel) + if err := preflightRequiredPorts(cfg, portnetwork.TryBindToPort); err != nil { + logger.L.Fatal().Err(err).Msg("startup_port_preflight_failed") + } d := db.SetupDatabase(cfg, false) _ = db.SetupCache(cfg) @@ -95,6 +98,10 @@ func main() { zeltaS := serviceRegistry.ZeltaService clusterSvc := cS.(*cluster.Service) + if err := clusterSvc.MigrateLegacyPorts(); err != nil { + logger.L.Fatal().Err(err).Msg("failed_to_migrate_legacy_cluster_ports") + } + jailSvc := jS.(*jail.Service) libvirtSvc := lvS.(*libvirt.Service) lifecycleSvc := lifecycle.NewService(d, libvirtSvc, jailSvc) @@ -133,11 +140,7 @@ func main() { err = cS.InitRaft(fsm) if err != nil { - if !strings.Contains(err.Error(), "record not found") { - logger.L.Error().Err(err).Msg("Failed to initialize RAFT") - } else { - logger.L.Info().Msg("Not initializing RAFT") - } + logger.L.Fatal().Err(err).Msg("Failed to initialize RAFT") } if err := clusterSvc.StartEmbeddedSSHServer(qCtx); err != nil { @@ -169,7 +172,7 @@ func main() { iS.(*info.Service), zS.(*zfs.Service), dS.(*disk.Service), - nS.(*network.Service), + nS.(*networkService.Service), uS.(*utilities.Service), sysS.(*system.Service), libvirtSvc, @@ -190,7 +193,7 @@ func main() { Auth: aS.(*auth.Service), Jail: jailSvc, VirtualMachine: libvirtSvc, - Network: nS.(*network.Service), + Network: nS.(*networkService.Service), QuitChan: sigChan, } @@ -220,13 +223,6 @@ func main() { TLSConfig: tlsConfig, } - if cfg.HTTPPort == cluster.ClusterEmbeddedHTTPSPort { - logger.L.Fatal(). - Int("http_port", cfg.HTTPPort). - Int("intra_cluster_https_port", cluster.ClusterEmbeddedHTTPSPort). - Msg("Configured HTTP port conflicts with reserved intra-cluster HTTPS port") - } - var wg sync.WaitGroup type namedServer struct { name string @@ -258,20 +254,15 @@ func main() { }() } - startDedicatedClusterHTTPS := cfg.Port == 0 || cfg.Port != cluster.ClusterEmbeddedHTTPSPort - if startDedicatedClusterHTTPS { - startedServers = append(startedServers, namedServer{name: "Intra-cluster HTTPS", srv: clusterHTTPSServer}) - wg.Add(1) - go func() { - defer wg.Done() - logger.L.Info().Msgf("Intra-cluster HTTPS server started on %s:%d", cfg.IP, cluster.ClusterEmbeddedHTTPSPort) - if err := clusterHTTPSServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { - logger.L.Fatal().Err(err).Msg("Failed to start intra-cluster HTTPS server") - } - }() - } else { - logger.L.Info().Msgf("Intra-cluster HTTPS is served by configured HTTPS port on %s:%d", cfg.IP, cfg.Port) - } + startedServers = append(startedServers, namedServer{name: "Intra-cluster HTTPS", srv: clusterHTTPSServer}) + wg.Add(1) + go func() { + defer wg.Done() + logger.L.Info().Msgf("Intra-cluster HTTPS server started on %s:%d", cfg.IP, cluster.ClusterEmbeddedHTTPSPort) + if err := clusterHTTPSServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { + logger.L.Fatal().Err(err).Msg("Failed to start intra-cluster HTTPS server") + } + }() <-sigChan diff --git a/cmd/sylve/ports_preflight.go b/cmd/sylve/ports_preflight.go new file mode 100644 index 00000000..91bedd60 --- /dev/null +++ b/cmd/sylve/ports_preflight.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package main + +import ( + "fmt" + "sort" + "strings" + + "github.com/alchemillahq/sylve/internal" + "github.com/alchemillahq/sylve/internal/services/cluster" + "github.com/alchemillahq/sylve/pkg/utils" +) + +type portRequirement struct { + role string + port int +} + +type portBinder func(ip string, port int, proto string) error + +func preflightRequiredPorts(cfg *internal.SylveConfig, binder portBinder) error { + reqs, err := buildPortRequirements(cfg) + if err != nil { + return err + } + + for _, req := range reqs { + if err := binder("", req.port, "tcp"); err != nil { + return fmt.Errorf("required_port_not_bindable role=%s port=%d: %w", req.role, req.port, err) + } + } + + return nil +} + +func buildPortRequirements(cfg *internal.SylveConfig) ([]portRequirement, error) { + if cfg == nil { + return nil, fmt.Errorf("config_required") + } + + reqs := make([]portRequirement, 0, 5) + + if cfg.HTTPPort != 0 { + if !utils.IsValidPort(cfg.HTTPPort) { + return nil, fmt.Errorf("invalid_http_port: %d", cfg.HTTPPort) + } + reqs = append(reqs, portRequirement{role: "http", port: cfg.HTTPPort}) + } + + if cfg.Port != 0 { + if !utils.IsValidPort(cfg.Port) { + return nil, fmt.Errorf("invalid_https_port: %d", cfg.Port) + } + reqs = append(reqs, portRequirement{role: "https", port: cfg.Port}) + } + + reqs = append(reqs, + portRequirement{role: "cluster_ssh", port: cluster.ClusterEmbeddedSSHPort}, + portRequirement{role: "raft", port: cluster.ClusterRaftPort}, + portRequirement{role: "cluster_https", port: cluster.ClusterEmbeddedHTTPSPort}, + ) + + for _, req := range reqs { + if !utils.IsValidPort(req.port) { + return nil, fmt.Errorf("invalid_required_port role=%s port=%d", req.role, req.port) + } + } + + roleByPort := make(map[int][]string, len(reqs)) + for _, req := range reqs { + roleByPort[req.port] = append(roleByPort[req.port], req.role) + } + + ports := make([]int, 0, len(roleByPort)) + for port := range roleByPort { + ports = append(ports, port) + } + sort.Ints(ports) + + conflicts := make([]string, 0) + for _, port := range ports { + roles := roleByPort[port] + if len(roles) <= 1 { + continue + } + sort.Strings(roles) + conflicts = append(conflicts, fmt.Sprintf("port=%d roles=%s", port, strings.Join(roles, ","))) + } + + if len(conflicts) > 0 { + return nil, fmt.Errorf("port_role_collision: %s", strings.Join(conflicts, "; ")) + } + + return reqs, nil +} diff --git a/cmd/sylve/ports_preflight_test.go b/cmd/sylve/ports_preflight_test.go new file mode 100644 index 00000000..c3de5ead --- /dev/null +++ b/cmd/sylve/ports_preflight_test.go @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package main + +import ( + "errors" + "strings" + "testing" + + "github.com/alchemillahq/sylve/internal" + "github.com/alchemillahq/sylve/internal/services/cluster" +) + +func TestBuildPortRequirementsIncludesConfiguredAndFixedPorts(t *testing.T) { + cfg := &internal.SylveConfig{ + Port: 8181, + HTTPPort: 8182, + } + + reqs, err := buildPortRequirements(cfg) + if err != nil { + t.Fatalf("buildPortRequirements returned error: %v", err) + } + + rolesByPort := map[int]string{} + for _, req := range reqs { + rolesByPort[req.port] = req.role + } + + expected := map[int]string{ + 8181: "https", + 8182: "http", + cluster.ClusterRaftPort: "raft", + cluster.ClusterEmbeddedSSHPort: "cluster_ssh", + cluster.ClusterEmbeddedHTTPSPort: "cluster_https", + } + + if len(rolesByPort) != len(expected) { + t.Fatalf("expected %d unique ports, got %d", len(expected), len(rolesByPort)) + } + + for port, role := range expected { + gotRole, ok := rolesByPort[port] + if !ok { + t.Fatalf("missing required role %q on port %d", role, port) + } + if gotRole != role { + t.Fatalf("unexpected role for port %d: expected %q got %q", port, role, gotRole) + } + } +} + +func TestBuildPortRequirementsAllowsDisabledHTTPAndHTTPS(t *testing.T) { + cfg := &internal.SylveConfig{Port: 0, HTTPPort: 0} + + reqs, err := buildPortRequirements(cfg) + if err != nil { + t.Fatalf("buildPortRequirements returned error: %v", err) + } + + if len(reqs) != 3 { + t.Fatalf("expected only fixed cluster ports when HTTP/HTTPS disabled, got %d", len(reqs)) + } + + ports := map[int]struct{}{} + for _, req := range reqs { + ports[req.port] = struct{}{} + } + + for _, requiredPort := range []int{cluster.ClusterRaftPort, cluster.ClusterEmbeddedSSHPort, cluster.ClusterEmbeddedHTTPSPort} { + if _, ok := ports[requiredPort]; !ok { + t.Fatalf("missing required fixed port %d", requiredPort) + } + } +} + +func TestBuildPortRequirementsDetectsRoleCollision(t *testing.T) { + cfg := &internal.SylveConfig{ + Port: cluster.ClusterEmbeddedHTTPSPort, + HTTPPort: 8182, + } + + _, err := buildPortRequirements(cfg) + if err == nil { + t.Fatal("expected collision error, got nil") + } + + if !strings.Contains(err.Error(), "port_role_collision") { + t.Fatalf("expected port_role_collision error, got: %v", err) + } + if !strings.Contains(err.Error(), "cluster_https") || !strings.Contains(err.Error(), "https") { + t.Fatalf("expected collision error to include colliding roles, got: %v", err) + } +} + +func TestPreflightRequiredPortsFailsOnBindError(t *testing.T) { + cfg := &internal.SylveConfig{ + Port: 8181, + HTTPPort: 8182, + } + + err := preflightRequiredPorts(cfg, func(_ string, port int, proto string) error { + if proto != "tcp" { + t.Fatalf("expected tcp bind checks, got %q", proto) + } + if port == cluster.ClusterRaftPort { + return errors.New("already in use") + } + return nil + }) + + if err == nil { + t.Fatal("expected preflight bind error, got nil") + } + if !strings.Contains(err.Error(), "role=raft") || !strings.Contains(err.Error(), "port=8180") { + t.Fatalf("expected role-specific bind failure, got: %v", err) + } +} + +func TestPreflightRequiredPortsChecksAllExpectedRoles(t *testing.T) { + cfg := &internal.SylveConfig{ + Port: 8181, + HTTPPort: 8182, + } + + called := map[int]struct{}{} + err := preflightRequiredPorts(cfg, func(_ string, port int, proto string) error { + if proto != "tcp" { + t.Fatalf("expected tcp bind checks, got %q", proto) + } + called[port] = struct{}{} + return nil + }) + if err != nil { + t.Fatalf("preflightRequiredPorts returned error: %v", err) + } + + for _, expectedPort := range []int{8181, 8182, cluster.ClusterRaftPort, cluster.ClusterEmbeddedSSHPort, cluster.ClusterEmbeddedHTTPSPort} { + if _, ok := called[expectedPort]; !ok { + t.Fatalf("missing bind check for port %d", expectedPort) + } + } +} diff --git a/docs/app-docs/src/content/docs/getting-started.mdx b/docs/app-docs/src/content/docs/getting-started.mdx index ea94a64d..7bc5502a 100644 --- a/docs/app-docs/src/content/docs/getting-started.mdx +++ b/docs/app-docs/src/content/docs/getting-started.mdx @@ -114,8 +114,8 @@ We will setup an `rc` script for running Sylve but prior to that we need to do 2 "keyFile": "/usr/local/etc/letsencrypt/live/sylve.example.net/privkey.pem", }, "logLevel": 3, - "port": 8181, // You can set to 0 to disable HTTPS entirely - "httpPort": 8182, // You can set to 0 to disable HTTP entirely + "port": 8181, // You can set to 0 to disable the main/public HTTPS listener + "httpPort": 8182, // You can set to 0 to disable the main/public HTTP listener "raft": { "reset": false, }, @@ -284,6 +284,10 @@ Sylve uses these ports by default: - `8182/tcp`: Main API and web UI over HTTP only. This is insecure and can break features like passkeys and serial/VNC console, so use it only when needed (for example, behind a reverse proxy on a trusted network). Change in `config.json` with `httpPort`. -- `8180/tcp`: RAFT communication between cluster nodes. You can choose this value during cluster setup, but every node must use the same RAFT port. +- `8180/tcp`: RAFT communication between cluster nodes. This port is fixed in Sylve and is not configurable. + +- `8183/tcp`: Cluster SSH channel used for intra-cluster operations. This port is fixed in Sylve and is not configurable. + +- `8184/tcp`: Intra-cluster HTTPS API used for node-to-node control plane traffic (including cluster join flows). This port is fixed in Sylve and is not configurable. - `7246/udp`: BTT DHT peer discovery. Used only when `btt.dht.enabled` is `true`. Change in `config.json` with `btt.dht.port`. diff --git a/docs/app-docs/src/content/docs/guides/data-center/clustering/index.mdx b/docs/app-docs/src/content/docs/guides/data-center/clustering/index.mdx index 743f7585..b6776e62 100644 --- a/docs/app-docs/src/content/docs/guides/data-center/clustering/index.mdx +++ b/docs/app-docs/src/content/docs/guides/data-center/clustering/index.mdx @@ -23,7 +23,7 @@ The upcoming replication feature is also disabled on nodes with less than 3 node ## Setting up a Cluster -Setting up a cluster is pretty straightforward, all you need to do is to navigate to Datacenter -> Cluster and then click on "Create Cluster". Once you do that a modal opens up where you can fill out the IP of your node that will be used for clustering and the port as well, this port must be **identical** on all nodes in the cluster, the default port is 8180. +Setting up a cluster is pretty straightforward, all you need to do is to navigate to Datacenter -> Cluster and then click on "Create Cluster". Once you do that a modal opens up where you can fill out the IP of your node that will be used for clustering. RAFT uses the fixed intra-cluster port `8180/tcp` on every node. ![Creating a cluster](create-cluster.png) @@ -39,7 +39,7 @@ Treat the cluster key like a password, anyone with the cluster key can join the ## Joining a Cluster -To join a cluster, copy the cluster key from the first node or the **LEADER NODE** and click on "Join Cluster" on the node that you want to join the cluster. This will open up a modal where you can paste the cluster key, and the API of the **LEADER NODE**, the API is NOT running on port 8180 or whatever your RAFT port is, it will be running on "192.168.172.203:8181" or whatever ip:port you use to access the WebUI of the leader. Once that is done click on "Join". Once you do that, the node will attempt to join the cluster and if successful, it will be added to the cluster view. This is what the join modal looks like: +To join a cluster, copy the cluster key from the first node or the **LEADER NODE** and click on "Join Cluster" on the node that you want to join the cluster. This opens a modal where you provide the cluster key and the IP of the **LEADER NODE** (IP only, no port). Sylve uses the fixed intra-cluster HTTPS port `8184/tcp` internally for leader API communication, and fixed RAFT port `8180/tcp` for consensus. Once that is done click on "Join". Once you do that, the node will attempt to join the cluster and if successful, it will be added to the cluster view. This is what the join modal looks like: ![Joining a cluster](join-cluster.png) @@ -63,4 +63,4 @@ Once you join a cluster successfully, your `/datacenter/summary` dashboard shoul ## Testing out RAFT -We have a nice little "Notes" application similar to the Notes application found inside each node, but the difference with this one is that the data is RAFT replicated meaning if you make a note on one node it should eventually be present on all Nodes. This is the best litmus test to see if everything is fine before you proceed with more interesting things. \ No newline at end of file +We have a nice little "Notes" application similar to the Notes application found inside each node, but the difference with this one is that the data is RAFT replicated meaning if you make a note on one node it should eventually be present on all Nodes. This is the best litmus test to see if everything is fine before you proceed with more interesting things. diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 0d33d598..c6a5a243 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -646,7 +646,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Create a cluster given a bootstrapping nodes IP and Port", + "description": "Create a cluster given a bootstrapping node IP", "consumes": [ "application/json" ], @@ -13301,8 +13301,7 @@ const docTemplate = `{ "required": [ "clusterKey", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { @@ -13313,11 +13312,6 @@ const docTemplate = `{ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, @@ -13325,16 +13319,15 @@ const docTemplate = `{ "type": "object", "required": [ "clusterKey", - "leaderApi", + "leaderIp", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { "type": "string" }, - "leaderApi": { + "leaderIp": { "type": "string" }, "nodeId": { @@ -13342,11 +13335,6 @@ const docTemplate = `{ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 81c8cded..2ca6c3f6 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -640,7 +640,7 @@ "BearerAuth": [] } ], - "description": "Create a cluster given a bootstrapping nodes IP and Port", + "description": "Create a cluster given a bootstrapping node IP", "consumes": [ "application/json" ], @@ -13295,8 +13295,7 @@ "required": [ "clusterKey", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { @@ -13307,11 +13306,6 @@ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, @@ -13319,16 +13313,15 @@ "type": "object", "required": [ "clusterKey", - "leaderApi", + "leaderIp", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { "type": "string" }, - "leaderApi": { + "leaderIp": { "type": "string" }, "nodeId": { @@ -13336,11 +13329,6 @@ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 0a11b0ab..dd365f16 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -3221,36 +3221,26 @@ definitions: type: string nodeIp: type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer required: - clusterKey - nodeId - nodeIp - - nodePort type: object internal_handlers_cluster.JoinClusterRequest: properties: clusterKey: type: string - leaderApi: + leaderIp: type: string nodeId: type: string nodeIp: type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer required: - clusterKey - - leaderApi + - leaderIp - nodeId - nodeIp - - nodePort type: object internal_handlers_cluster.NoteRequest: properties: @@ -4293,7 +4283,7 @@ paths: post: consumes: - application/json - description: Create a cluster given a bootstrapping nodes IP and Port + description: Create a cluster given a bootstrapping node IP produces: - application/json responses: diff --git a/internal/assets/swagger/swagger.json b/internal/assets/swagger/swagger.json index 070933b8..2ca6c3f6 100644 --- a/internal/assets/swagger/swagger.json +++ b/internal/assets/swagger/swagger.json @@ -13,7 +13,7 @@ "name": "BSD-2-Clause", "url": "https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE" }, - "version": "0.0.1" + "version": "0.1.1" }, "host": "sylve.lan:8181", "basePath": "/api", @@ -156,6 +156,57 @@ } } }, + "/auth/groups/members": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update the members of a specified group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Groups" + ], + "summary": "Update Group Members", + "parameters": [ + { + "description": "Update group members request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_auth.AddUsersToGroupRequest" + } + } + ], + "responses": { + "200": { + "description": "Group members updated successfully", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/auth/groups/users": { "post": { "security": [ @@ -589,7 +640,7 @@ "BearerAuth": [] } ], - "description": "Create a cluster given a bootstrapping nodes IP and Port", + "description": "Create a cluster given a bootstrapping node IP", "consumes": [ "application/json" ], @@ -883,6 +934,46 @@ } } }, + "/cluster/resync-state": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Replays current cluster-backed state through Raft and forces a snapshot from the leader", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster" + ], + "summary": "Resync Cluster State", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/disk/create-partitions": { "post": { "security": [ @@ -1490,6 +1581,40 @@ } } }, + "/info/node": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get the node information about the system (mainly for cluster stuff)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "Get Node Info", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_info_BasicInfo" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/info/notes": { "get": { "security": [ @@ -1681,6 +1806,62 @@ } }, "/info/notes/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing note in the cluster", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster" + ], + "summary": "Update a Cluster Note", + "parameters": [ + { + "type": "integer", + "description": "Note ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Note Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_cluster.NoteRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, "delete": { "security": [ { @@ -1855,6 +2036,60 @@ } } }, + "/intra-cluster/sync-health": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Internal endpoint used by the Raft Leader to broadcast cluster health to followers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster Internal" + ], + "summary": "Sync Cluster Health", + "parameters": [ + { + "description": "Array of node health states", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync" + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/jail": { "get": { "security": [ @@ -2245,6 +2480,49 @@ } }, "/jail/network": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Edit a network switch on a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Edit Network Switch for Jail", + "parameters": [ + { + "description": "Edit Network Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, "post": { "security": [ { @@ -2469,6 +2747,72 @@ } } }, + "/jail/simple/:id": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a simple jail by its CTID or ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Get a Jail by CTID or ID (Simple)", + "parameters": [ + { + "type": "string", + "description": "Jail CTID or ID", + "name": "identifier", + "in": "path", + "required": true + }, + { + "enum": [ + "ctid", + "id" + ], + "type": "string", + "default": "ctid", + "description": "Type of identifier (ctid or id)", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/jail/state": { "get": { "security": [ @@ -3732,6 +4076,46 @@ } } }, + "/network/update": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update a network interface attached to a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Update Network Switch for a Virtual Machine", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/additional-options/:rid": { "put": { "security": [ @@ -3783,6 +4167,57 @@ } } }, + "/options/allowed-options/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify allowed options configuration of a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify Allowed Options of a Jail", + "parameters": [ + { + "description": "Modify Allowed Options Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyAllowedOptionsRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/boot-order/:rid": { "put": { "security": [ @@ -4089,6 +4524,57 @@ } } }, + "/options/lifecycle-hooks/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify jail exec.* lifecycle hooks in one request", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify Lifecycle Hooks of a Jail", + "parameters": [ + { + "description": "Modify Lifecycle Hooks Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyLifecycleHooksRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/metadata/:rid": { "put": { "security": [ @@ -4140,6 +4626,108 @@ } } }, + "/options/qemu-guest-agent/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify the QEMU Guest Agent configuration of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Modify QEMU Guest Agent of a Virtual Machine", + "parameters": [ + { + "description": "Modify QEMU Guest Agent Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_vm.ModifyQemuGuestAgentRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/options/resolv-conf/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify /etc/resolv.conf content for a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify resolv.conf of a Jail", + "parameters": [ + { + "description": "Modify resolv.conf Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyResolvConfRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/serial-console/:rid": { "put": { "security": [ @@ -4242,6 +4830,57 @@ } } }, + "/options/tpm/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify the TPM configuration of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Modify TPM of a Virtual Machine", + "parameters": [ + { + "description": "Modify TPM Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_vm.ModifyTPMRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/wol/:rid": { "put": { "security": [ @@ -4293,6 +4932,46 @@ } } }, + "/qga/:rid": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve QEMU Guest Agent OS and network info of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Get QEMU Guest Agent info of a Virtual Machine", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/samba/audit-logs": { "get": { "description": "Retrieve Samba audit logs", @@ -5446,6 +6125,108 @@ } } }, + "/system/ppt-devices/import": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Import an existing ppt device into Sylve passthrough management", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "Import Passed Through Device", + "parameters": [ + { + "description": "Device ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_system.AddPassthroughDeviceRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/system/ppt-devices/prepare": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a device to loader.conf for passthrough on next boot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "Prepare Passed Through Device", + "parameters": [ + { + "description": "Device ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_system.AddPassthroughDeviceRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/system/ppt-devices/{id}": { "delete": { "security": [ @@ -5495,6 +6276,181 @@ } } }, + "/utilities/cloud-init/templates": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List all Cloud-Init templates", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "List Cloud-Init Templates", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new Cloud-Init template", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Add Cloud-Init Template", + "parameters": [ + { + "description": "Add Template Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/utilities/cloud-init/templates/:id": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Edit an existing Cloud-Init template", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Edit Cloud-Init Template", + "parameters": [ + { + "description": "Edit Template Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a Cloud-Init template by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Delete Cloud-Init Template", + "parameters": [ + { + "type": "integer", + "description": "Template ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/utilities/downloads": { "get": { "security": [ @@ -6076,6 +7032,66 @@ } } }, + "/vm/simple/:id": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a simple virtual machine object by its RID or ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Get a simple Virtual Machine by RID or ID", + "parameters": [ + { + "type": "string", + "description": "Virtual Machine RID or ID", + "name": "id", + "in": "path", + "required": true + }, + { + "enum": [ + "rid", + "id" + ], + "type": "string", + "default": "rid", + "description": "Type of identifier (rid or id)", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/vm/stats/:rid/:step": { "get": { "security": [ @@ -7755,6 +8771,26 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate" + } + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_Downloads": { "type": "object", "properties": { @@ -8165,6 +9201,23 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.SimpleList" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_State": { "type": "object", "properties": { @@ -8199,6 +9252,23 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_network_Leases": { "type": "object", "properties": { @@ -8473,6 +9543,12 @@ "email": { "type": "string" }, + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group" + } + }, "id": { "type": "integer" }, @@ -8543,6 +9619,12 @@ "diskUsage": { "type": "number" }, + "guestIDs": { + "type": "array", + "items": { + "type": "integer" + } + }, "hostname": { "type": "string" }, @@ -8767,9 +9849,18 @@ "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.Network" } }, + "resolvConf": { + "type": "string" + }, "resourceLimits": { "type": "boolean" }, + "snapshots": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot" + } + }, "startAtBoot": { "type": "boolean" }, @@ -8827,6 +9918,9 @@ "enabled": { "type": "boolean" }, + "id": { + "type": "integer" + }, "jid": { "type": "integer" }, @@ -8838,6 +9932,41 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "ctId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "jid": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentSnapshotId": { + "type": "integer" + }, + "rootDataset": { + "type": "string" + }, + "snapshotName": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_jail.JailStats": { "type": "object", "properties": { @@ -8914,12 +10043,18 @@ "macObj": { "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object" }, + "manualSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch" + }, "name": { "type": "string" }, "slaac": { "type": "boolean" }, + "standardSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch" + }, "switchId": { "type": "integer" }, @@ -9101,6 +10236,10 @@ "isUsed": { "type": "boolean" }, + "isUsedBy": { + "description": "\"\", \"dhcp\" for now", + "type": "string" + }, "name": { "type": "string" }, @@ -9355,6 +10494,32 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "networkConfig": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_utilities.DownloadStatus": { "type": "string", "enum": [ @@ -9496,6 +10661,12 @@ "macObj": { "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object" }, + "manualSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch" + }, + "standardSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch" + }, "switchId": { "type": "integer" }, @@ -9577,6 +10748,9 @@ "cloudInitMetaData": { "type": "string" }, + "cloudInitNetworkConfig": { + "type": "string" + }, "cpuCores": { "type": "integer" }, @@ -9619,6 +10793,9 @@ "type": "integer" } }, + "qemuGuestAgent": { + "type": "boolean" + }, "ram": { "type": "integer" }, @@ -9631,6 +10808,12 @@ "shutdownWaitTime": { "type": "integer" }, + "snapshots": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot" + } + }, "startAtBoot": { "type": "boolean" }, @@ -9701,6 +10884,44 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentSnapshotId": { + "type": "integer" + }, + "rid": { + "type": "integer" + }, + "rootDatasets": { + "type": "array", + "items": { + "type": "string" + } + }, + "snapshotName": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "vmId": { + "type": "integer" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_vm.VMStats": { "type": "object", "properties": { @@ -9855,6 +11076,47 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "cpu": { + "type": "integer" + }, + "cpuUsage": { + "type": "number" + }, + "disk": { + "type": "integer" + }, + "diskUsage": { + "type": "number" + }, + "guestIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "hostname": { + "type": "string" + }, + "memory": { + "type": "integer" + }, + "memoryUsage": { + "type": "number" + }, + "nodeUuid": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeResources": { "type": "object", "properties": { @@ -9884,6 +11146,12 @@ "address": { "type": "string" }, + "guestIDs": { + "type": "array", + "items": { + "type": "integer" + } + }, "id": { "type": "string" }, @@ -9951,6 +11219,33 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture": { + "type": "string", + "enum": [ + "amd64", + "386", + "arm", + "arm64", + "riscv64", + "ppc64", + "ppc64le", + "s390x", + "wasm", + "loong64" + ], + "x-enum-varnames": [ + "ArchAMD64", + "Arch386", + "ArchARM", + "ArchARM64", + "ArchRISCV64", + "ArchPPC64", + "ArchPPC64LE", + "ArchS390X", + "ArchWASM", + "ArchLOONG64" + ] + }, "github_com_alchemillahq_sylve_internal_interfaces_services_info.BasicInfo": { "type": "object", "properties": { @@ -9977,6 +11272,9 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_info.CPUInfo": { "type": "object", "properties": { + "architecture": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture" + }, "cache": { "type": "object", "properties": { @@ -10201,6 +11499,9 @@ "pool": { "type": "string" }, + "resolvConf": { + "type": "string" + }, "resourceLimits": { "type": "boolean" }, @@ -10221,6 +11522,49 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest": { + "type": "object", + "required": [ + "name", + "networkId", + "switchName" + ], + "properties": { + "defaultGateway": { + "type": "boolean" + }, + "dhcp": { + "type": "boolean" + }, + "ip4": { + "type": "integer" + }, + "ip4gw": { + "type": "integer" + }, + "ip6": { + "type": "integer" + }, + "ip6gw": { + "type": "integer" + }, + "macId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "networkId": { + "type": "integer" + }, + "slaac": { + "type": "boolean" + }, + "switchName": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_jail.HookPhase": { "type": "object", "properties": { @@ -10337,6 +11681,9 @@ "cloudInitMetaData": { "type": "string" }, + "cloudInitNetworkConfig": { + "type": "string" + }, "cpuCores": { "type": "integer" }, @@ -10373,6 +11720,9 @@ "type": "integer" } }, + "qemuGuestAgent": { + "type": "boolean" + }, "ram": { "type": "integer" }, @@ -10496,6 +11846,12 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList": { "type": "object", "properties": { + "cpuPinning": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMCPUPinning" + } + }, "id": { "type": "integer" }, @@ -10794,7 +12150,6 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_system.InitializeRequest": { "type": "object", "required": [ - "pools", "services" ], "properties": { @@ -10812,6 +12167,49 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest": { + "type": "object", + "required": [ + "meta", + "name", + "user" + ], + "properties": { + "meta": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "networkConfig": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest": { + "type": "object", + "properties": { + "meta": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "networkConfig": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.UTypeGroupedDownload": { "type": "object", "properties": { @@ -10875,7 +12273,8 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_zfs.CreateZPoolRequest": { "type": "object", "required": [ - "name" + "name", + "raidType" ], "properties": { "createForce": { @@ -11896,8 +13295,7 @@ "required": [ "clusterKey", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { @@ -11908,15 +13306,33 @@ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, - "internal_handlers_cluster.CreateNoteRequest": { + "internal_handlers_cluster.JoinClusterRequest": { + "type": "object", + "required": [ + "clusterKey", + "leaderIp", + "nodeId", + "nodeIp" + ], + "properties": { + "clusterKey": { + "type": "string" + }, + "leaderIp": { + "type": "string" + }, + "nodeId": { + "type": "string" + }, + "nodeIp": { + "type": "string" + } + } + }, + "internal_handlers_cluster.NoteRequest": { "type": "object", "required": [ "content", @@ -11933,35 +13349,6 @@ } } }, - "internal_handlers_cluster.JoinClusterRequest": { - "type": "object", - "required": [ - "clusterKey", - "leaderApi", - "nodeId", - "nodeIp", - "nodePort" - ], - "properties": { - "clusterKey": { - "type": "string" - }, - "leaderApi": { - "type": "string" - }, - "nodeId": { - "type": "string" - }, - "nodeIp": { - "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 - } - } - }, "internal_handlers_cluster.RemovePeerRequest": { "type": "object", "required": [ @@ -12096,6 +13483,17 @@ } } }, + "internal_handlers_jail.ModifyAllowedOptionsRequest": { + "type": "object", + "properties": { + "allowedOptions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "internal_handlers_jail.ModifyBootOrderRequest": { "type": "object", "properties": { @@ -12123,6 +13521,14 @@ } } }, + "internal_handlers_jail.ModifyLifecycleHooksRequest": { + "type": "object", + "properties": { + "hooks": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.Hooks" + } + } + }, "internal_handlers_jail.ModifyMetadataRequest": { "type": "object", "properties": { @@ -12134,6 +13540,14 @@ } } }, + "internal_handlers_jail.ModifyResolvConfRequest": { + "type": "object", + "properties": { + "resolvConf": { + "type": "string" + } + } + }, "internal_handlers_jail.SetInheritanceRequest": { "type": "object", "properties": { @@ -12509,6 +13923,9 @@ }, "metadata": { "type": "string" + }, + "networkConfig": { + "type": "string" } } }, @@ -12534,6 +13951,14 @@ } } }, + "internal_handlers_vm.ModifyQemuGuestAgentRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "internal_handlers_vm.ModifyRAMRequest": { "type": "object", "required": [ @@ -12561,6 +13986,14 @@ } } }, + "internal_handlers_vm.ModifyTPMRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "internal_handlers_vm.ModifyWakeOnLanRequest": { "type": "object", "properties": { diff --git a/internal/assets/swagger/swagger.yaml b/internal/assets/swagger/swagger.yaml index 73d9e5b6..dd365f16 100644 --- a/internal/assets/swagger/swagger.yaml +++ b/internal/assets/swagger/swagger.yaml @@ -1,11 +1,3 @@ -# SPDX-License-Identifier: BSD-2-Clause -# -# Copyright (c) 2025 The FreeBSD Foundation. -# -# This software was developed by Hayzam Sherif -# of Alchemilla Ventures Pvt. Ltd. , -# under sponsorship from the FreeBSD Foundation. - basePath: /api definitions: github_com_alchemillahq_sylve_internal.APIResponse-any: @@ -200,6 +192,19 @@ definitions: status: type: string type: object + ? github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate + : properties: + data: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate' + type: array + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_Downloads: properties: data: @@ -466,6 +471,17 @@ definitions: status: type: string type: object + github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList: + properties: + data: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.SimpleList' + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_State: properties: data: @@ -488,6 +504,17 @@ definitions: status: type: string type: object + github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList: + properties: + data: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList' + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_network_Leases: properties: data: @@ -669,6 +696,10 @@ definitions: type: string email: type: string + groups: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group' + type: array id: type: integer lastLoginTime: @@ -715,6 +746,10 @@ definitions: type: integer diskUsage: type: number + guestIDs: + items: + type: integer + type: array hostname: type: string id: @@ -862,8 +897,14 @@ definitions: items: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.Network' type: array + resolvConf: + type: string resourceLimits: type: boolean + snapshots: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot' + type: array startAtBoot: type: boolean startLogs: @@ -905,6 +946,8 @@ definitions: properties: enabled: type: boolean + id: + type: integer jid: type: integer phase: @@ -912,6 +955,29 @@ definitions: script: type: string type: object + github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot: + properties: + createdAt: + type: string + ctId: + type: integer + description: + type: string + id: + type: integer + jid: + type: integer + name: + type: string + parentSnapshotId: + type: integer + rootDataset: + type: string + snapshotName: + type: string + updatedAt: + type: string + type: object github_com_alchemillahq_sylve_internal_db_models_jail.JailStats: properties: cpuUsage: @@ -963,10 +1029,14 @@ definitions: type: integer macObj: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object' + manualSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch' name: type: string slaac: type: boolean + standardSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch' switchId: type: integer switchType: @@ -1087,6 +1157,9 @@ definitions: type: integer isUsed: type: boolean + isUsedBy: + description: '"", "dhcp" for now' + type: string name: type: string resolutions: @@ -1255,6 +1328,23 @@ definitions: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group' type: array type: object + github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate: + properties: + createdAt: + type: string + id: + type: integer + meta: + type: string + name: + type: string + networkConfig: + type: string + updatedAt: + type: string + user: + type: string + type: object github_com_alchemillahq_sylve_internal_db_models_utilities.DownloadStatus: enum: - pending @@ -1353,6 +1443,10 @@ definitions: type: integer macObj: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object' + manualSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch' + standardSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch' switchId: type: integer switchType: @@ -1407,6 +1501,8 @@ definitions: type: string cloudInitMetaData: type: string + cloudInitNetworkConfig: + type: string cpuCores: type: integer cpuPinning: @@ -1435,6 +1531,8 @@ definitions: items: type: integer type: array + qemuGuestAgent: + type: boolean ram: type: integer rid: @@ -1443,6 +1541,10 @@ definitions: type: boolean shutdownWaitTime: type: integer + snapshots: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot' + type: array startAtBoot: type: boolean startOrder: @@ -1489,6 +1591,31 @@ definitions: vmId: type: integer type: object + github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot: + properties: + createdAt: + type: string + description: + type: string + id: + type: integer + name: + type: string + parentSnapshotId: + type: integer + rid: + type: integer + rootDatasets: + items: + type: string + type: array + snapshotName: + type: string + updatedAt: + type: string + vmId: + type: integer + type: object github_com_alchemillahq_sylve_internal_db_models_vm.VMStats: properties: cpuUsage: @@ -1596,6 +1723,33 @@ definitions: partial: type: boolean type: object + github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync: + properties: + api: + type: string + cpu: + type: integer + cpuUsage: + type: number + disk: + type: integer + diskUsage: + type: number + guestIds: + items: + type: integer + type: array + hostname: + type: string + memory: + type: integer + memoryUsage: + type: number + nodeUuid: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeResources: properties: hostname: @@ -1615,6 +1769,10 @@ definitions: properties: address: type: string + guestIDs: + items: + type: integer + type: array id: type: string isLeader: @@ -1659,6 +1817,30 @@ definitions: uuid: type: string type: object + github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture: + enum: + - amd64 + - "386" + - arm + - arm64 + - riscv64 + - ppc64 + - ppc64le + - s390x + - wasm + - loong64 + type: string + x-enum-varnames: + - ArchAMD64 + - Arch386 + - ArchARM + - ArchARM64 + - ArchRISCV64 + - ArchPPC64 + - ArchPPC64LE + - ArchS390X + - ArchWASM + - ArchLOONG64 github_com_alchemillahq_sylve_internal_interfaces_services_info.BasicInfo: properties: bootMode: @@ -1676,6 +1858,8 @@ definitions: type: object github_com_alchemillahq_sylve_internal_interfaces_services_info.CPUInfo: properties: + architecture: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture' cache: properties: l1d: @@ -1820,6 +2004,8 @@ definitions: type: string pool: type: string + resolvConf: + type: string resourceLimits: type: boolean slaac: @@ -1838,6 +2024,35 @@ definitions: - pool - type type: object + github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest: + properties: + defaultGateway: + type: boolean + dhcp: + type: boolean + ip4: + type: integer + ip4gw: + type: integer + ip6: + type: integer + ip6gw: + type: integer + macId: + type: integer + name: + type: string + networkId: + type: integer + slaac: + type: boolean + switchName: + type: string + required: + - name + - networkId + - switchName + type: object github_com_alchemillahq_sylve_internal_interfaces_services_jail.HookPhase: properties: enabled: @@ -1908,6 +2123,8 @@ definitions: type: string cloudInitMetaData: type: string + cloudInitNetworkConfig: + type: string cpuCores: type: integer cpuPinning: @@ -1932,6 +2149,8 @@ definitions: items: type: integer type: array + qemuGuestAgent: + type: boolean ram: type: integer rid: @@ -2023,6 +2242,10 @@ definitions: type: object github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList: properties: + cpuPinning: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMCPUPinning' + type: array id: type: integer name: @@ -2233,9 +2456,38 @@ definitions: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.AvailableService' type: array required: - - pools - services type: object + github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest: + properties: + meta: + type: string + name: + maxLength: 255 + minLength: 1 + type: string + networkConfig: + type: string + user: + type: string + required: + - meta + - name + - user + type: object + github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest: + properties: + meta: + type: string + name: + maxLength: 255 + minLength: 1 + type: string + networkConfig: + type: string + user: + type: string + type: object github_com_alchemillahq_sylve_internal_interfaces_services_utilities.UTypeGroupedDownload: properties: label: @@ -2300,6 +2552,7 @@ definitions: type: array required: - name + - raidType type: object github_com_alchemillahq_sylve_internal_interfaces_services_zfs.EditVolumeRequest: properties: @@ -2968,17 +3221,28 @@ definitions: type: string nodeIp: type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer required: - clusterKey - nodeId - nodeIp - - nodePort type: object - internal_handlers_cluster.CreateNoteRequest: + internal_handlers_cluster.JoinClusterRequest: + properties: + clusterKey: + type: string + leaderIp: + type: string + nodeId: + type: string + nodeIp: + type: string + required: + - clusterKey + - leaderIp + - nodeId + - nodeIp + type: object + internal_handlers_cluster.NoteRequest: properties: content: minLength: 3 @@ -2990,27 +3254,6 @@ definitions: - content - title type: object - internal_handlers_cluster.JoinClusterRequest: - properties: - clusterKey: - type: string - leaderApi: - type: string - nodeId: - type: string - nodeIp: - type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer - required: - - clusterKey - - leaderApi - - nodeId - - nodeIp - - nodePort - type: object internal_handlers_cluster.RemovePeerRequest: properties: nodeId: @@ -3099,6 +3342,13 @@ definitions: additionalOptions: type: string type: object + internal_handlers_jail.ModifyAllowedOptionsRequest: + properties: + allowedOptions: + items: + type: string + type: array + type: object internal_handlers_jail.ModifyBootOrderRequest: properties: bootOrder: @@ -3116,6 +3366,11 @@ definitions: fstab: type: string type: object + internal_handlers_jail.ModifyLifecycleHooksRequest: + properties: + hooks: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.Hooks' + type: object internal_handlers_jail.ModifyMetadataRequest: properties: env: @@ -3123,6 +3378,11 @@ definitions: metadata: type: string type: object + internal_handlers_jail.ModifyResolvConfRequest: + properties: + resolvConf: + type: string + type: object internal_handlers_jail.SetInheritanceRequest: properties: ipv4: @@ -3372,6 +3632,8 @@ definitions: type: string metadata: type: string + networkConfig: + type: string type: object internal_handlers_vm.ModifyIgnoreUMSRsRequest: properties: @@ -3387,6 +3649,11 @@ definitions: required: - pciDevices type: object + internal_handlers_vm.ModifyQemuGuestAgentRequest: + properties: + enabled: + type: boolean + type: object internal_handlers_vm.ModifyRAMRequest: properties: ram: @@ -3404,6 +3671,11 @@ definitions: waitTime: type: integer type: object + internal_handlers_vm.ModifyTPMRequest: + properties: + enabled: + type: boolean + type: object internal_handlers_vm.ModifyWakeOnLanRequest: properties: enabled: @@ -3618,7 +3890,7 @@ info: url: https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE termsOfService: https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE title: Sylve API - version: 0.0.1 + version: 0.1.1 paths: /auth/groups: get: @@ -3707,6 +3979,38 @@ paths: summary: Delete Group tags: - Groups + /auth/groups/members: + put: + consumes: + - application/json + description: Update the members of a specified group + parameters: + - description: Update group members request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_auth.AddUsersToGroupRequest' + produces: + - application/json + responses: + "200": + description: Group members updated successfully + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update Group Members + tags: + - Groups /auth/groups/users: post: consumes: @@ -3979,7 +4283,7 @@ paths: post: consumes: - application/json - description: Create a cluster given a bootstrapping nodes IP and Port + description: Create a cluster given a bootstrapping node IP produces: - application/json responses: @@ -4163,6 +4467,32 @@ paths: summary: Get Cluster Resources (per node) tags: - Cluster + /cluster/resync-state: + post: + consumes: + - application/json + description: Replays current cluster-backed state through Raft and forces a + snapshot from the leader + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "409": + description: Conflict + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Resync Cluster State + tags: + - Cluster /disk/create-partitions: post: consumes: @@ -4544,6 +4874,27 @@ paths: summary: Get Historical Network information tags: - system + /info/node: + get: + consumes: + - application/json + description: Get the node information about the system (mainly for cluster stuff) + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_info_BasicInfo' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get Node Info + tags: + - Info /info/notes: get: consumes: @@ -4669,6 +5020,42 @@ paths: summary: Delete a Cluster Note tags: - Cluster + put: + consumes: + - application/json + description: Update an existing note in the cluster + parameters: + - description: Note ID + in: path + name: id + required: true + type: integer + - description: Update Note Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_cluster.NoteRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update a Cluster Note + tags: + - Cluster /info/notes/bulk-delete: post: consumes: @@ -4774,6 +5161,41 @@ paths: summary: Get Historical Swap information tags: - system + /intra-cluster/sync-health: + post: + consumes: + - application/json + description: Internal endpoint used by the Raft Leader to broadcast cluster + health to followers + parameters: + - description: Array of node health states + in: body + name: payload + required: true + schema: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync' + type: array + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Sync Cluster Health + tags: + - Cluster Internal /jail: get: consumes: @@ -5088,6 +5510,33 @@ paths: summary: Add Network Switch to Jail tags: - Jail + put: + consumes: + - application/json + description: Edit a network switch on a jail + parameters: + - description: Edit Network Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Edit Network Switch for Jail + tags: + - Jail /jail/network/{ctId}/{networkId}: delete: consumes: @@ -5203,6 +5652,49 @@ paths: summary: List all Jails (Simple) tags: - Jail + /jail/simple/:id: + get: + consumes: + - application/json + description: Retrieve a simple jail by its CTID or ID + parameters: + - description: Jail CTID or ID + in: path + name: identifier + required: true + type: string + - default: ctid + description: Type of identifier (ctid or id) + enum: + - ctid + - id + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get a Jail by CTID or ID (Simple) + tags: + - Jail /jail/state: get: consumes: @@ -5961,6 +6453,31 @@ paths: summary: Delete a Standard Switch tags: - Network + /network/update: + put: + consumes: + - application/json + description: Update a network interface attached to a virtual machine + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update Network Switch for a Virtual Machine + tags: + - VM /options/additional-options/:rid: put: consumes: @@ -5993,6 +6510,38 @@ paths: summary: Modify Additional Options of a Jail tags: - Jail + /options/allowed-options/:rid: + put: + consumes: + - application/json + description: Modify allowed options configuration of a jail + parameters: + - description: Modify Allowed Options Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyAllowedOptionsRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify Allowed Options of a Jail + tags: + - Jail /options/boot-order/:rid: put: consumes: @@ -6185,6 +6734,38 @@ paths: summary: Modify Ignore UMSRs of a Virtual Machine tags: - VM + /options/lifecycle-hooks/:rid: + put: + consumes: + - application/json + description: Modify jail exec.* lifecycle hooks in one request + parameters: + - description: Modify Lifecycle Hooks Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyLifecycleHooksRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify Lifecycle Hooks of a Jail + tags: + - Jail /options/metadata/:rid: put: consumes: @@ -6217,6 +6798,70 @@ paths: summary: Modify Metadata of a Jail tags: - Jail + /options/qemu-guest-agent/:rid: + put: + consumes: + - application/json + description: Modify the QEMU Guest Agent configuration of a virtual machine + parameters: + - description: Modify QEMU Guest Agent Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_vm.ModifyQemuGuestAgentRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify QEMU Guest Agent of a Virtual Machine + tags: + - VM + /options/resolv-conf/:rid: + put: + consumes: + - application/json + description: Modify /etc/resolv.conf content for a jail + parameters: + - description: Modify resolv.conf Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyResolvConfRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify resolv.conf of a Jail + tags: + - Jail /options/serial-console/:rid: put: consumes: @@ -6281,6 +6926,38 @@ paths: summary: Modify Shutdown Wait Time of a Virtual Machine tags: - VM + /options/tpm/:rid: + put: + consumes: + - application/json + description: Modify the TPM configuration of a virtual machine + parameters: + - description: Modify TPM Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_vm.ModifyTPMRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify TPM of a Virtual Machine + tags: + - VM /options/wol/:rid: put: consumes: @@ -6313,6 +6990,31 @@ paths: summary: Modify Wake-on-LAN of a Virtual Machine tags: - VM + /qga/:rid: + get: + consumes: + - application/json + description: Retrieve QEMU Guest Agent OS and network info of a virtual machine + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get QEMU Guest Agent info of a Virtual Machine + tags: + - VM /samba/audit-logs: get: consumes: @@ -7080,6 +7782,180 @@ paths: summary: Remove Passed Through Device tags: - System + /system/ppt-devices/import: + post: + consumes: + - application/json + description: Import an existing ppt device into Sylve passthrough management + parameters: + - description: Device ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_system.AddPassthroughDeviceRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Import Passed Through Device + tags: + - System + /system/ppt-devices/prepare: + post: + consumes: + - application/json + description: Add a device to loader.conf for passthrough on next boot + parameters: + - description: Device ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_system.AddPassthroughDeviceRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Prepare Passed Through Device + tags: + - System + /utilities/cloud-init/templates: + get: + consumes: + - application/json + description: List all Cloud-Init templates + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: List Cloud-Init Templates + tags: + - Utilities + post: + consumes: + - application/json + description: Add a new Cloud-Init template + parameters: + - description: Add Template Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Add Cloud-Init Template + tags: + - Utilities + /utilities/cloud-init/templates/:id: + delete: + consumes: + - application/json + description: Delete a Cloud-Init template by ID + parameters: + - description: Template ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Delete Cloud-Init Template + tags: + - Utilities + put: + consumes: + - application/json + description: Edit an existing Cloud-Init template + parameters: + - description: Edit Template Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Edit Cloud-Init Template + tags: + - Utilities /utilities/downloads: get: consumes: @@ -7528,6 +8404,45 @@ paths: summary: List all VMs (Simple) tags: - VM + /vm/simple/:id: + get: + consumes: + - application/json + description: Retrieve a simple virtual machine object by its RID or ID + parameters: + - description: Virtual Machine RID or ID + in: path + name: id + required: true + type: string + - default: rid + description: Type of identifier (rid or id) + enum: + - rid + - id + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get a simple Virtual Machine by RID or ID + tags: + - VM /vm/stats/:rid/:step: get: consumes: diff --git a/internal/db/db.go b/internal/db/db.go index c7462777..b0fb93a6 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -301,7 +301,7 @@ func initClusterRecord(db *gorm.DB) error { Key: "", RaftBootstrap: nil, RaftIP: "", - RaftPort: 0, + RaftPort: 8180, } if err := db.Create(defaultCluster).Error; err != nil { diff --git a/internal/db/models/cluster/replication.go b/internal/db/models/cluster/replication.go index 8159809f..b68c5dc6 100644 --- a/internal/db/models/cluster/replication.go +++ b/internal/db/models/cluster/replication.go @@ -159,7 +159,7 @@ type ClusterSSHIdentity struct { NodeUUID string `gorm:"uniqueIndex;not null" json:"nodeUUID"` SSHUser string `gorm:"not null;default:root" json:"sshUser"` SSHHost string `gorm:"not null" json:"sshHost"` - SSHPort int `gorm:"not null;default:8122" json:"sshPort"` + SSHPort int `gorm:"not null;default:8183" json:"sshPort"` PublicKey string `gorm:"type:text;not null" json:"publicKey"` CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` @@ -414,7 +414,7 @@ func upsertClusterSSHIdentity(db *gorm.DB, identity *ClusterSSHIdentity) error { identity.SSHUser = "root" } if identity.SSHPort == 0 { - identity.SSHPort = 8122 + identity.SSHPort = 8183 } if identity.NodeUUID == "" { diff --git a/internal/handlers/cluster/cluster.go b/internal/handlers/cluster/cluster.go index b816b2c6..5ac16202 100644 --- a/internal/handlers/cluster/cluster.go +++ b/internal/handlers/cluster/cluster.go @@ -24,22 +24,19 @@ import ( ) type CreateClusterRequest struct { - IP string `json:"ip" binding:"required,ip"` - Port int `json:"port" binding:"required,min=1024,max=65535"` + IP string `json:"ip" binding:"required,ip"` } type JoinClusterRequest struct { NodeID string `json:"nodeId" binding:"required"` NodeIP string `json:"nodeIp" binding:"required,ip"` - NodePort int `json:"nodePort" binding:"required,min=1024,max=65535"` - LeaderAPI string `json:"leaderApi" binding:"required"` + LeaderIP string `json:"leaderIp" binding:"required,ip"` ClusterKey string `json:"clusterKey" binding:"required"` } type AcceptJoinRequest struct { NodeID string `json:"nodeId" binding:"required"` NodeIP string `json:"nodeIp" binding:"required,ip"` - NodePort int `json:"nodePort" binding:"required,min=1024,max=65535"` ClusterKey string `json:"clusterKey" binding:"required"` } @@ -47,6 +44,10 @@ type RemovePeerRequest struct { NodeID string `json:"nodeId" binding:"required"` } +func joinLeaderAPIHost(leaderIP string) string { + return cluster.ClusterAPIHost(leaderIP) +} + // @Summary Get Cluster // @Description Get cluster details with information about RAFT nodes too // @Tags Cluster @@ -79,7 +80,7 @@ func GetCluster(cS *cluster.Service) gin.HandlerFunc { } // @Summary Create Cluster -// @Description Create a cluster given a bootstrapping nodes IP and Port +// @Description Create a cluster given a bootstrapping node IP // @Tags Cluster // @Accept json // @Produce json @@ -101,7 +102,7 @@ func CreateCluster(as *auth.Service, cS *cluster.Service, fsm raft.FSM) gin.Hand return } - if err := cS.CreateCluster(req.IP, req.Port, fsm); err != nil { + if err := cS.CreateCluster(req.IP, fsm); err != nil { c.JSON(http.StatusInternalServerError, internal.APIResponse[any]{ Status: "error", Message: "error_creating_cluster", @@ -170,16 +171,18 @@ func JoinCluster(aS *auth.Service, cS *cluster.Service, zS *zelta.Service, fsm r return } - if !utils.IsValidIPPort(req.LeaderAPI) { + if !utils.IsValidIP(req.LeaderIP) { c.JSON(http.StatusBadRequest, internal.APIResponse[any]{ Status: "error", - Message: "invalid_leader_api", - Error: "leader_api_must_be_in_host_port_format", + Message: "invalid_leader_ip", + Error: "leader_ip_must_be_valid", Data: nil, }) return } + leaderAPIHost := joinLeaderAPIHost(req.LeaderIP) + userId := c.GetUint("UserID") username := c.GetString("Username") authType := c.GetString("AuthType") @@ -190,7 +193,7 @@ func JoinCluster(aS *auth.Service, cS *cluster.Service, zS *zelta.Service, fsm r healthURL := fmt.Sprintf( "https://%s/api/health/basic", - req.LeaderAPI, + leaderAPIHost, ) if err := utils.HTTPPostJSON(healthURL, req, headers); err != nil { @@ -203,7 +206,7 @@ func JoinCluster(aS *auth.Service, cS *cluster.Service, zS *zelta.Service, fsm r return } - err = cS.StartAsJoiner(fsm, req.NodeIP, req.NodePort, req.ClusterKey) + err = cS.StartAsJoiner(fsm, req.NodeIP, req.ClusterKey) if err != nil { c.JSON(http.StatusInternalServerError, internal.APIResponse[any]{ Status: "error", @@ -214,11 +217,10 @@ func JoinCluster(aS *auth.Service, cS *cluster.Service, zS *zelta.Service, fsm r return } - acceptURL := fmt.Sprintf("https://%s/api/cluster/accept-join", req.LeaderAPI) + acceptURL := fmt.Sprintf("https://%s/api/cluster/accept-join", leaderAPIHost) payload := map[string]any{ "nodeId": req.NodeID, "nodeIp": req.NodeIP, - "nodePort": req.NodePort, "clusterKey": req.ClusterKey, } @@ -275,7 +277,7 @@ func AcceptJoin(cS *cluster.Service) gin.HandlerFunc { return } - if err := cS.AcceptJoin(req.NodeID, req.NodeIP, req.NodePort, req.ClusterKey); err != nil { + if err := cS.AcceptJoin(req.NodeID, req.NodeIP, req.ClusterKey); err != nil { if strings.HasPrefix(err.Error(), "not_leader;") { c.JSON(http.StatusConflict, internal.APIResponse[any]{ Status: "error", diff --git a/internal/handlers/cluster/cluster_join_test.go b/internal/handlers/cluster/cluster_join_test.go new file mode 100644 index 00000000..dfa08daf --- /dev/null +++ b/internal/handlers/cluster/cluster_join_test.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package clusterHandlers + +import ( + "encoding/json" + "net" + "net/http" + "strconv" + "testing" + + "github.com/alchemillahq/sylve/internal/services/cluster" + "github.com/gin-gonic/gin" +) + +func newClusterLifecycleValidationRouter() *gin.Engine { + gin.SetMode(gin.TestMode) + r := gin.New() + r.POST("/cluster", CreateCluster(nil, nil, nil)) + r.POST("/cluster/join", JoinCluster(nil, nil, nil, nil)) + r.POST("/cluster/accept-join", AcceptJoin(nil)) + return r +} + +func TestCreateClusterRejectsPayloadWithoutIP(t *testing.T) { + r := newClusterLifecycleValidationRouter() + + rr := performJSONRequest(t, r, http.MethodPost, "/cluster", []byte(`{"port":8180}`)) + if rr.Code != http.StatusBadRequest { + t.Fatalf("expected status 400, got %d with body %s", rr.Code, rr.Body.String()) + } + + var resp handlerAPIResponse[any] + if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { + t.Fatalf("invalid json response: %v", err) + } + if resp.Message != "invalid_request_payload" { + t.Fatalf("expected invalid_request_payload, got %q", resp.Message) + } +} + +func TestJoinClusterRejectsLegacyLeaderApiPayload(t *testing.T) { + r := newClusterLifecycleValidationRouter() + + body := []byte(`{"nodeId":"node-1","nodeIp":"10.0.0.2","leaderApi":"10.0.0.1:8184","nodePort":8180,"clusterKey":"secret"}`) + rr := performJSONRequest(t, r, http.MethodPost, "/cluster/join", body) + if rr.Code != http.StatusBadRequest { + t.Fatalf("expected status 400, got %d with body %s", rr.Code, rr.Body.String()) + } + + var resp handlerAPIResponse[any] + if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { + t.Fatalf("invalid json response: %v", err) + } + if resp.Message != "invalid_request_payload" { + t.Fatalf("expected invalid_request_payload, got %q", resp.Message) + } +} + +func TestAcceptJoinRejectsPayloadWithoutNodeIP(t *testing.T) { + r := newClusterLifecycleValidationRouter() + + rr := performJSONRequest(t, r, http.MethodPost, "/cluster/accept-join", []byte(`{"nodeId":"node-1","clusterKey":"secret"}`)) + if rr.Code != http.StatusBadRequest { + t.Fatalf("expected status 400, got %d with body %s", rr.Code, rr.Body.String()) + } + + var resp handlerAPIResponse[any] + if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { + t.Fatalf("invalid json response: %v", err) + } + if resp.Message != "invalid_request_payload" { + t.Fatalf("expected invalid_request_payload, got %q", resp.Message) + } +} + +func TestJoinLeaderAPIHostUsesClusterHTTPSPort(t *testing.T) { + tests := []struct { + name string + ip string + }{ + {name: "ipv4", ip: "10.0.0.9"}, + {name: "ipv6", ip: "fd00::9"}, + {name: "trimmed", ip: " 192.168.10.20 "}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hostPort := joinLeaderAPIHost(tt.ip) + host, port, err := net.SplitHostPort(hostPort) + if err != nil { + t.Fatalf("SplitHostPort failed for %q: %v", hostPort, err) + } + if host == "" { + t.Fatal("expected non-empty host") + } + if port != strconv.Itoa(cluster.ClusterEmbeddedHTTPSPort) { + t.Fatalf("expected cluster HTTPS port %d, got %s", cluster.ClusterEmbeddedHTTPSPort, port) + } + }) + } +} diff --git a/internal/interfaces/services/cluster/cluster.go b/internal/interfaces/services/cluster/cluster.go index 5f879760..fe4661be 100644 --- a/internal/interfaces/services/cluster/cluster.go +++ b/internal/interfaces/services/cluster/cluster.go @@ -48,7 +48,7 @@ type NodeResources struct { type ClusterServiceInterface interface { Detail() *Detail InitRaft(fsm raft.FSM) error - CreateCluster(ip string, port int, fsm raft.FSM) error + CreateCluster(ip string, fsm raft.FSM) error SetupRaft(bootstrap bool, fsm raft.FSM) (*raft.Raft, error) GetClusterDetails() (*ClusterDetails, error) PopulateClusterNodes() error diff --git a/internal/services/cluster/cluster.go b/internal/services/cluster/cluster.go index 3990b030..a9c58870 100644 --- a/internal/services/cluster/cluster.go +++ b/internal/services/cluster/cluster.go @@ -346,11 +346,13 @@ func (s *Service) ResyncClusterState() error { return nil } -func (s *Service) CreateCluster(ip string, port int, fsm raft.FSM) error { +func (s *Service) CreateCluster(ip string, fsm raft.FSM) error { if s.Raft != nil { return errors.New("raft_already_initialized") } + port := ClusterRaftPort + if err := network.TryBindToPort(ip, port, "tcp"); err != nil { return err } @@ -419,14 +421,12 @@ func (s *Service) CreateCluster(ip string, port int, fsm raft.FSM) error { return nil } -func (s *Service) StartAsJoiner(fsm raft.FSM, ip string, port int, clusterKey string) error { +func (s *Service) StartAsJoiner(fsm raft.FSM, ip string, clusterKey string) error { if !utils.IsValidIP(ip) { return errors.New("invalid_ip_address") } - if !utils.IsValidPort(port) { - return errors.New("invalid_port_number") - } + port := ClusterRaftPort if err := network.TryBindToPort(ip, port, "tcp"); err != nil { return fmt.Errorf("failed_to_bind_to_port: %v", err) @@ -471,7 +471,7 @@ func (s *Service) StartAsJoiner(fsm raft.FSM, ip string, port int, clusterKey st _, err = s.SetupRaft(false, fsm) if err != nil { c.RaftIP = "" - c.RaftPort = 0 + c.RaftPort = ClusterRaftPort c.Enabled = false c.Key = "" @@ -539,7 +539,7 @@ func (s *Service) ClearClusteredData() error { }) } -func (s *Service) AcceptJoin(nodeID, nodeIp string, nodePort int, providedKey string) error { +func (s *Service) AcceptJoin(nodeID, nodeIp string, providedKey string) error { details, err := s.GetClusterDetails() if err != nil { return err @@ -569,7 +569,7 @@ func (s *Service) AcceptJoin(nodeID, nodeIp string, nodePort int, providedKey st conf := fut.Configuration() sid := raft.ServerID(nodeID) - saddr := raft.ServerAddress(fmt.Sprintf("%s:%d", nodeIp, nodePort)) + saddr := raft.ServerAddress(RaftServerAddress(nodeIp)) for _, srv := range conf.Servers { if srv.ID == sid { @@ -623,7 +623,7 @@ func (s *Service) MarkDeclustered() error { c.Key = "" c.RaftBootstrap = nil c.RaftIP = "" - c.RaftPort = 0 + c.RaftPort = ClusterRaftPort if err := s.DB.Save(&c).Error; err != nil { return err diff --git a/internal/services/cluster/migration.go b/internal/services/cluster/migration.go new file mode 100644 index 00000000..f7705779 --- /dev/null +++ b/internal/services/cluster/migration.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package cluster + +import ( + "fmt" + + clusterModels "github.com/alchemillahq/sylve/internal/db/models/cluster" + "gorm.io/gorm" +) + +const legacyClusterEmbeddedSSHPort = 8122 + +func (s *Service) MigrateLegacyPorts() error { + if s == nil || s.DB == nil { + return fmt.Errorf("cluster_service_unavailable") + } + + return s.DB.Transaction(func(tx *gorm.DB) error { + var c clusterModels.Cluster + if err := tx.First(&c).Error; err != nil { + return fmt.Errorf("failed_to_load_cluster_record: %w", err) + } + + if c.RaftPort != ClusterRaftPort { + if err := tx.Model(&c).Update("raft_port", ClusterRaftPort).Error; err != nil { + return fmt.Errorf("failed_to_migrate_raft_port: %w", err) + } + } + + if err := tx.Model(&clusterModels.ClusterSSHIdentity{}). + Where("ssh_port IN ?", []int{0, legacyClusterEmbeddedSSHPort}). + Update("ssh_port", ClusterEmbeddedSSHPort).Error; err != nil { + return fmt.Errorf("failed_to_migrate_cluster_ssh_identity_ports: %w", err) + } + + return nil + }) +} diff --git a/internal/services/cluster/migration_test.go b/internal/services/cluster/migration_test.go new file mode 100644 index 00000000..d65fe719 --- /dev/null +++ b/internal/services/cluster/migration_test.go @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package cluster + +import ( + "strings" + "testing" + + clusterModels "github.com/alchemillahq/sylve/internal/db/models/cluster" +) + +func TestMigrateLegacyPortsRewritesRaftAndSSHPorts(t *testing.T) { + db := newClusterServiceTestDB(t, &clusterModels.Cluster{}, &clusterModels.ClusterSSHIdentity{}) + s := &Service{DB: db} + + clusterRecord := clusterModels.Cluster{ + Enabled: false, + Key: "", + RaftIP: "", + RaftPort: 7000, + } + if err := db.Create(&clusterRecord).Error; err != nil { + t.Fatalf("failed to seed cluster record: %v", err) + } + + identities := []clusterModels.ClusterSSHIdentity{ + {NodeUUID: "node-a", SSHUser: "root", SSHHost: "10.0.0.10", SSHPort: 0, PublicKey: "pk-a"}, + {NodeUUID: "node-b", SSHUser: "root", SSHHost: "10.0.0.11", SSHPort: legacyClusterEmbeddedSSHPort, PublicKey: "pk-b"}, + {NodeUUID: "node-c", SSHUser: "root", SSHHost: "10.0.0.12", SSHPort: 2222, PublicKey: "pk-c"}, + } + if err := db.Create(&identities).Error; err != nil { + t.Fatalf("failed to seed cluster ssh identities: %v", err) + } + + if err := s.MigrateLegacyPorts(); err != nil { + t.Fatalf("MigrateLegacyPorts returned error: %v", err) + } + + var updatedCluster clusterModels.Cluster + if err := db.First(&updatedCluster, clusterRecord.ID).Error; err != nil { + t.Fatalf("failed to fetch migrated cluster record: %v", err) + } + if updatedCluster.RaftPort != ClusterRaftPort { + t.Fatalf("expected raft port %d, got %d", ClusterRaftPort, updatedCluster.RaftPort) + } + + var migrated []clusterModels.ClusterSSHIdentity + if err := db.Order("node_uuid ASC").Find(&migrated).Error; err != nil { + t.Fatalf("failed to fetch migrated cluster ssh identities: %v", err) + } + if len(migrated) != 3 { + t.Fatalf("expected 3 migrated identities, got %d", len(migrated)) + } + + for _, identity := range migrated { + switch identity.NodeUUID { + case "node-a", "node-b": + if identity.SSHPort != ClusterEmbeddedSSHPort { + t.Fatalf("expected %s SSH port to be migrated to %d, got %d", identity.NodeUUID, ClusterEmbeddedSSHPort, identity.SSHPort) + } + case "node-c": + if identity.SSHPort != 2222 { + t.Fatalf("expected non-legacy SSH port to remain 2222, got %d", identity.SSHPort) + } + default: + t.Fatalf("unexpected node UUID in migrated identities: %s", identity.NodeUUID) + } + } +} + +func TestMigrateLegacyPortsFailsWhenClusterRecordMissing(t *testing.T) { + db := newClusterServiceTestDB(t, &clusterModels.Cluster{}, &clusterModels.ClusterSSHIdentity{}) + s := &Service{DB: db} + + err := s.MigrateLegacyPorts() + if err == nil { + t.Fatal("expected migration error when cluster record is missing, got nil") + } + if !strings.Contains(err.Error(), "failed_to_load_cluster_record") { + t.Fatalf("expected failed_to_load_cluster_record error, got: %v", err) + } +} + +func TestMigrateLegacyPortsFailsWhenServiceUnavailable(t *testing.T) { + s := &Service{} + + err := s.MigrateLegacyPorts() + if err == nil { + t.Fatal("expected service unavailable error, got nil") + } + if !strings.Contains(err.Error(), "cluster_service_unavailable") { + t.Fatalf("expected cluster_service_unavailable error, got: %v", err) + } +} diff --git a/internal/services/cluster/ports.go b/internal/services/cluster/ports.go index ff9d2fca..feb6add8 100644 --- a/internal/services/cluster/ports.go +++ b/internal/services/cluster/ports.go @@ -8,7 +8,22 @@ package cluster -const ( - ClusterEmbeddedSSHPort = 8122 - ClusterEmbeddedHTTPSPort = 8124 +import ( + "net" + "strconv" + "strings" ) + +const ( + ClusterRaftPort = 8180 + ClusterEmbeddedSSHPort = 8183 + ClusterEmbeddedHTTPSPort = 8184 +) + +func ClusterAPIHost(ip string) string { + return net.JoinHostPort(strings.TrimSpace(ip), strconv.Itoa(ClusterEmbeddedHTTPSPort)) +} + +func RaftServerAddress(ip string) string { + return net.JoinHostPort(strings.TrimSpace(ip), strconv.Itoa(ClusterRaftPort)) +} diff --git a/internal/services/cluster/ports_test.go b/internal/services/cluster/ports_test.go new file mode 100644 index 00000000..6fc8bf15 --- /dev/null +++ b/internal/services/cluster/ports_test.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package cluster + +import ( + "net" + "strconv" + "testing" +) + +func TestRaftServerAddressUsesFixedPort(t *testing.T) { + tests := []struct { + name string + ip string + }{ + {name: "ipv4", ip: "10.20.30.40"}, + {name: "ipv6", ip: "::1"}, + {name: "trimmed", ip: " 192.168.1.50 "}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := RaftServerAddress(tt.ip) + host, port, err := net.SplitHostPort(addr) + if err != nil { + t.Fatalf("SplitHostPort failed for %q: %v", addr, err) + } + if port != strconv.Itoa(ClusterRaftPort) { + t.Fatalf("expected raft port %d, got %s", ClusterRaftPort, port) + } + if host == "" { + t.Fatal("expected non-empty host") + } + }) + } +} + +func TestClusterAPIHostUsesFixedHTTPSPort(t *testing.T) { + tests := []struct { + name string + ip string + }{ + {name: "ipv4", ip: "10.20.30.41"}, + {name: "ipv6", ip: "fd00::abcd"}, + {name: "trimmed", ip: " 192.168.1.51 "}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hostPort := ClusterAPIHost(tt.ip) + host, port, err := net.SplitHostPort(hostPort) + if err != nil { + t.Fatalf("SplitHostPort failed for %q: %v", hostPort, err) + } + if port != strconv.Itoa(ClusterEmbeddedHTTPSPort) { + t.Fatalf("expected cluster API port %d, got %s", ClusterEmbeddedHTTPSPort, port) + } + if host == "" { + t.Fatal("expected non-empty host") + } + }) + } +} diff --git a/internal/services/cluster/raft.go b/internal/services/cluster/raft.go index fa45f548..2329b736 100644 --- a/internal/services/cluster/raft.go +++ b/internal/services/cluster/raft.go @@ -49,7 +49,9 @@ func (s *Service) SetupRaft(bootstrap bool, fsm raft.FSM) (*raft.Raft, error) { return nil, fmt.Errorf("failed_to_get_cluster_info: %v", err) } - err := network.TryBindToPort(c.RaftIP, c.RaftPort, "tcp") + port := ClusterRaftPort + + err := network.TryBindToPort(c.RaftIP, port, "tcp") if err != nil { return nil, fmt.Errorf("failed_to_bind_raft_port: %v", err) } @@ -84,7 +86,7 @@ func (s *Service) SetupRaft(bootstrap bool, fsm raft.FSM) (*raft.Raft, error) { return nil, fmt.Errorf("failed_to_create_snap_store") } - bindAddr := fmt.Sprintf("%s:%d", c.RaftIP, c.RaftPort) + bindAddr := RaftServerAddress(c.RaftIP) tcpAddr, err := net.ResolveTCPAddr("tcp", bindAddr) if err != nil { return nil, fmt.Errorf("Could not resolve address: %s", err) @@ -252,7 +254,7 @@ func (s *Service) ResetRaftNode() error { } err = utils.HTTPPostJSON( - fmt.Sprintf("https://%s:%d/api/cluster/remove-peer", host, ClusterEmbeddedHTTPSPort), payload, headers) + fmt.Sprintf("https://%s/api/cluster/remove-peer", ClusterAPIHost(host)), payload, headers) if err != nil { return fmt.Errorf("failed_to_remove_peer_from_leader: %v", err) diff --git a/web/src/lib/api/cluster/cluster.ts b/web/src/lib/api/cluster/cluster.ts index 17666d31..e6d81904 100644 --- a/web/src/lib/api/cluster/cluster.ts +++ b/web/src/lib/api/cluster/cluster.ts @@ -14,25 +14,22 @@ export async function getDetails(): Promise { return await apiRequest('/cluster', ClusterDetailsSchema, 'GET'); } -export async function createCluster(ip: string, port: number): Promise { +export async function createCluster(ip: string): Promise { return await apiRequest('/cluster', APIResponseSchema, 'POST', { - ip: ip, - port: port + ip: ip }); } export async function joinCluster( nodeId: string, nodeIp: string, - nodePort: number, - leaderApi: string, + leaderIp: string, clusterKey: string ): Promise { return await apiRequest('/cluster/join', APIResponseSchema, 'POST', { nodeId: nodeId, nodeIp: nodeIp, - nodePort: nodePort, - leaderApi: leaderApi, + leaderIp: leaderIp, clusterKey: clusterKey }); } diff --git a/web/src/lib/components/custom/Cluster/Create.svelte b/web/src/lib/components/custom/Cluster/Create.svelte index 85d9d4bf..bc4ea2ce 100644 --- a/web/src/lib/components/custom/Cluster/Create.svelte +++ b/web/src/lib/components/custom/Cluster/Create.svelte @@ -5,8 +5,7 @@ import * as Dialog from '$lib/components/ui/dialog/index.js'; import { storage } from '$lib'; import { handleAPIError } from '$lib/utils/http'; - import { isValidIPv4, isValidIPv6, isValidPortNumber } from '$lib/utils/string'; - import { onMount } from 'svelte'; + import { isValidIPv4, isValidIPv6 } from '$lib/utils/string'; import { toast } from 'svelte-sonner'; import { logOut } from '$lib/api/auth'; @@ -17,8 +16,7 @@ let { open = $bindable(), reload = $bindable() }: Props = $props(); let options = { - ip: '', - port: 8180 + ip: '' }; let properties = $state(options); @@ -39,8 +37,6 @@ if (!isValidIPv4(properties.ip) && !isValidIPv6(properties.ip)) { error = 'Invalid IP address'; - } else if (!isValidPortNumber(properties.port)) { - error = 'Invalid port number'; } if (error) { @@ -53,7 +49,7 @@ loading = true; - const response = await createCluster(properties.ip, properties.port); + const response = await createCluster(properties.ip); reload = true; loading = false; if (response.error) { @@ -123,13 +119,6 @@ classes="flex-1 space-y-1.5" /> - -
@@ -153,8 +142,8 @@ diff --git a/web/static/swagger/swagger.json b/web/static/swagger/swagger.json index 070933b8..2ca6c3f6 100644 --- a/web/static/swagger/swagger.json +++ b/web/static/swagger/swagger.json @@ -13,7 +13,7 @@ "name": "BSD-2-Clause", "url": "https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE" }, - "version": "0.0.1" + "version": "0.1.1" }, "host": "sylve.lan:8181", "basePath": "/api", @@ -156,6 +156,57 @@ } } }, + "/auth/groups/members": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update the members of a specified group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Groups" + ], + "summary": "Update Group Members", + "parameters": [ + { + "description": "Update group members request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_auth.AddUsersToGroupRequest" + } + } + ], + "responses": { + "200": { + "description": "Group members updated successfully", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/auth/groups/users": { "post": { "security": [ @@ -589,7 +640,7 @@ "BearerAuth": [] } ], - "description": "Create a cluster given a bootstrapping nodes IP and Port", + "description": "Create a cluster given a bootstrapping node IP", "consumes": [ "application/json" ], @@ -883,6 +934,46 @@ } } }, + "/cluster/resync-state": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Replays current cluster-backed state through Raft and forces a snapshot from the leader", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster" + ], + "summary": "Resync Cluster State", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/disk/create-partitions": { "post": { "security": [ @@ -1490,6 +1581,40 @@ } } }, + "/info/node": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get the node information about the system (mainly for cluster stuff)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "Get Node Info", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_info_BasicInfo" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/info/notes": { "get": { "security": [ @@ -1681,6 +1806,62 @@ } }, "/info/notes/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing note in the cluster", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster" + ], + "summary": "Update a Cluster Note", + "parameters": [ + { + "type": "integer", + "description": "Note ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Note Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_cluster.NoteRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, "delete": { "security": [ { @@ -1855,6 +2036,60 @@ } } }, + "/intra-cluster/sync-health": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Internal endpoint used by the Raft Leader to broadcast cluster health to followers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Cluster Internal" + ], + "summary": "Sync Cluster Health", + "parameters": [ + { + "description": "Array of node health states", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync" + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/jail": { "get": { "security": [ @@ -2245,6 +2480,49 @@ } }, "/jail/network": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Edit a network switch on a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Edit Network Switch for Jail", + "parameters": [ + { + "description": "Edit Network Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, "post": { "security": [ { @@ -2469,6 +2747,72 @@ } } }, + "/jail/simple/:id": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a simple jail by its CTID or ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Get a Jail by CTID or ID (Simple)", + "parameters": [ + { + "type": "string", + "description": "Jail CTID or ID", + "name": "identifier", + "in": "path", + "required": true + }, + { + "enum": [ + "ctid", + "id" + ], + "type": "string", + "default": "ctid", + "description": "Type of identifier (ctid or id)", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/jail/state": { "get": { "security": [ @@ -3732,6 +4076,46 @@ } } }, + "/network/update": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update a network interface attached to a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Update Network Switch for a Virtual Machine", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/additional-options/:rid": { "put": { "security": [ @@ -3783,6 +4167,57 @@ } } }, + "/options/allowed-options/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify allowed options configuration of a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify Allowed Options of a Jail", + "parameters": [ + { + "description": "Modify Allowed Options Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyAllowedOptionsRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/boot-order/:rid": { "put": { "security": [ @@ -4089,6 +4524,57 @@ } } }, + "/options/lifecycle-hooks/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify jail exec.* lifecycle hooks in one request", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify Lifecycle Hooks of a Jail", + "parameters": [ + { + "description": "Modify Lifecycle Hooks Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyLifecycleHooksRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/metadata/:rid": { "put": { "security": [ @@ -4140,6 +4626,108 @@ } } }, + "/options/qemu-guest-agent/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify the QEMU Guest Agent configuration of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Modify QEMU Guest Agent of a Virtual Machine", + "parameters": [ + { + "description": "Modify QEMU Guest Agent Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_vm.ModifyQemuGuestAgentRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/options/resolv-conf/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify /etc/resolv.conf content for a jail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jail" + ], + "summary": "Modify resolv.conf of a Jail", + "parameters": [ + { + "description": "Modify resolv.conf Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_jail.ModifyResolvConfRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/serial-console/:rid": { "put": { "security": [ @@ -4242,6 +4830,57 @@ } } }, + "/options/tpm/:rid": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Modify the TPM configuration of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Modify TPM of a Virtual Machine", + "parameters": [ + { + "description": "Modify TPM Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_vm.ModifyTPMRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/options/wol/:rid": { "put": { "security": [ @@ -4293,6 +4932,46 @@ } } }, + "/qga/:rid": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve QEMU Guest Agent OS and network info of a virtual machine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Get QEMU Guest Agent info of a Virtual Machine", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/samba/audit-logs": { "get": { "description": "Retrieve Samba audit logs", @@ -5446,6 +6125,108 @@ } } }, + "/system/ppt-devices/import": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Import an existing ppt device into Sylve passthrough management", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "Import Passed Through Device", + "parameters": [ + { + "description": "Device ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_system.AddPassthroughDeviceRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/system/ppt-devices/prepare": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a device to loader.conf for passthrough on next boot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "Prepare Passed Through Device", + "parameters": [ + { + "description": "Device ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handlers_system.AddPassthroughDeviceRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/system/ppt-devices/{id}": { "delete": { "security": [ @@ -5495,6 +6276,181 @@ } } }, + "/utilities/cloud-init/templates": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "List all Cloud-Init templates", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "List Cloud-Init Templates", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new Cloud-Init template", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Add Cloud-Init Template", + "parameters": [ + { + "description": "Add Template Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, + "/utilities/cloud-init/templates/:id": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Edit an existing Cloud-Init template", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Edit Cloud-Init Template", + "parameters": [ + { + "description": "Edit Template Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a Cloud-Init template by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Utilities" + ], + "summary": "Delete Cloud-Init Template", + "parameters": [ + { + "type": "integer", + "description": "Template ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/utilities/downloads": { "get": { "security": [ @@ -6076,6 +7032,66 @@ } } }, + "/vm/simple/:id": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a simple virtual machine object by its RID or ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "VM" + ], + "summary": "Get a simple Virtual Machine by RID or ID", + "parameters": [ + { + "type": "string", + "description": "Virtual Machine RID or ID", + "name": "id", + "in": "path", + "required": true + }, + { + "enum": [ + "rid", + "id" + ], + "type": "string", + "default": "rid", + "description": "Type of identifier (rid or id)", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any" + } + } + } + } + }, "/vm/stats/:rid/:step": { "get": { "security": [ @@ -7755,6 +8771,26 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate" + } + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_Downloads": { "type": "object", "properties": { @@ -8165,6 +9201,23 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.SimpleList" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_State": { "type": "object", "properties": { @@ -8199,6 +9252,23 @@ } } }, + "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_network_Leases": { "type": "object", "properties": { @@ -8473,6 +9543,12 @@ "email": { "type": "string" }, + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group" + } + }, "id": { "type": "integer" }, @@ -8543,6 +9619,12 @@ "diskUsage": { "type": "number" }, + "guestIDs": { + "type": "array", + "items": { + "type": "integer" + } + }, "hostname": { "type": "string" }, @@ -8767,9 +9849,18 @@ "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.Network" } }, + "resolvConf": { + "type": "string" + }, "resourceLimits": { "type": "boolean" }, + "snapshots": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot" + } + }, "startAtBoot": { "type": "boolean" }, @@ -8827,6 +9918,9 @@ "enabled": { "type": "boolean" }, + "id": { + "type": "integer" + }, "jid": { "type": "integer" }, @@ -8838,6 +9932,41 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "ctId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "jid": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentSnapshotId": { + "type": "integer" + }, + "rootDataset": { + "type": "string" + }, + "snapshotName": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_jail.JailStats": { "type": "object", "properties": { @@ -8914,12 +10043,18 @@ "macObj": { "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object" }, + "manualSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch" + }, "name": { "type": "string" }, "slaac": { "type": "boolean" }, + "standardSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch" + }, "switchId": { "type": "integer" }, @@ -9101,6 +10236,10 @@ "isUsed": { "type": "boolean" }, + "isUsedBy": { + "description": "\"\", \"dhcp\" for now", + "type": "string" + }, "name": { "type": "string" }, @@ -9355,6 +10494,32 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "meta": { + "type": "string" + }, + "name": { + "type": "string" + }, + "networkConfig": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_utilities.DownloadStatus": { "type": "string", "enum": [ @@ -9496,6 +10661,12 @@ "macObj": { "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object" }, + "manualSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch" + }, + "standardSwitch": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch" + }, "switchId": { "type": "integer" }, @@ -9577,6 +10748,9 @@ "cloudInitMetaData": { "type": "string" }, + "cloudInitNetworkConfig": { + "type": "string" + }, "cpuCores": { "type": "integer" }, @@ -9619,6 +10793,9 @@ "type": "integer" } }, + "qemuGuestAgent": { + "type": "boolean" + }, "ram": { "type": "integer" }, @@ -9631,6 +10808,12 @@ "shutdownWaitTime": { "type": "integer" }, + "snapshots": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot" + } + }, "startAtBoot": { "type": "boolean" }, @@ -9701,6 +10884,44 @@ } } }, + "github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentSnapshotId": { + "type": "integer" + }, + "rid": { + "type": "integer" + }, + "rootDatasets": { + "type": "array", + "items": { + "type": "string" + } + }, + "snapshotName": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "vmId": { + "type": "integer" + } + } + }, "github_com_alchemillahq_sylve_internal_db_models_vm.VMStats": { "type": "object", "properties": { @@ -9855,6 +11076,47 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "cpu": { + "type": "integer" + }, + "cpuUsage": { + "type": "number" + }, + "disk": { + "type": "integer" + }, + "diskUsage": { + "type": "number" + }, + "guestIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "hostname": { + "type": "string" + }, + "memory": { + "type": "integer" + }, + "memoryUsage": { + "type": "number" + }, + "nodeUuid": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeResources": { "type": "object", "properties": { @@ -9884,6 +11146,12 @@ "address": { "type": "string" }, + "guestIDs": { + "type": "array", + "items": { + "type": "integer" + } + }, "id": { "type": "string" }, @@ -9951,6 +11219,33 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture": { + "type": "string", + "enum": [ + "amd64", + "386", + "arm", + "arm64", + "riscv64", + "ppc64", + "ppc64le", + "s390x", + "wasm", + "loong64" + ], + "x-enum-varnames": [ + "ArchAMD64", + "Arch386", + "ArchARM", + "ArchARM64", + "ArchRISCV64", + "ArchPPC64", + "ArchPPC64LE", + "ArchS390X", + "ArchWASM", + "ArchLOONG64" + ] + }, "github_com_alchemillahq_sylve_internal_interfaces_services_info.BasicInfo": { "type": "object", "properties": { @@ -9977,6 +11272,9 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_info.CPUInfo": { "type": "object", "properties": { + "architecture": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture" + }, "cache": { "type": "object", "properties": { @@ -10201,6 +11499,9 @@ "pool": { "type": "string" }, + "resolvConf": { + "type": "string" + }, "resourceLimits": { "type": "boolean" }, @@ -10221,6 +11522,49 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest": { + "type": "object", + "required": [ + "name", + "networkId", + "switchName" + ], + "properties": { + "defaultGateway": { + "type": "boolean" + }, + "dhcp": { + "type": "boolean" + }, + "ip4": { + "type": "integer" + }, + "ip4gw": { + "type": "integer" + }, + "ip6": { + "type": "integer" + }, + "ip6gw": { + "type": "integer" + }, + "macId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "networkId": { + "type": "integer" + }, + "slaac": { + "type": "boolean" + }, + "switchName": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_jail.HookPhase": { "type": "object", "properties": { @@ -10337,6 +11681,9 @@ "cloudInitMetaData": { "type": "string" }, + "cloudInitNetworkConfig": { + "type": "string" + }, "cpuCores": { "type": "integer" }, @@ -10373,6 +11720,9 @@ "type": "integer" } }, + "qemuGuestAgent": { + "type": "boolean" + }, "ram": { "type": "integer" }, @@ -10496,6 +11846,12 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList": { "type": "object", "properties": { + "cpuPinning": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMCPUPinning" + } + }, "id": { "type": "integer" }, @@ -10794,7 +12150,6 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_system.InitializeRequest": { "type": "object", "required": [ - "pools", "services" ], "properties": { @@ -10812,6 +12167,49 @@ } } }, + "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest": { + "type": "object", + "required": [ + "meta", + "name", + "user" + ], + "properties": { + "meta": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "networkConfig": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest": { + "type": "object", + "properties": { + "meta": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "networkConfig": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, "github_com_alchemillahq_sylve_internal_interfaces_services_utilities.UTypeGroupedDownload": { "type": "object", "properties": { @@ -10875,7 +12273,8 @@ "github_com_alchemillahq_sylve_internal_interfaces_services_zfs.CreateZPoolRequest": { "type": "object", "required": [ - "name" + "name", + "raidType" ], "properties": { "createForce": { @@ -11896,8 +13295,7 @@ "required": [ "clusterKey", "nodeId", - "nodeIp", - "nodePort" + "nodeIp" ], "properties": { "clusterKey": { @@ -11908,15 +13306,33 @@ }, "nodeIp": { "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 } } }, - "internal_handlers_cluster.CreateNoteRequest": { + "internal_handlers_cluster.JoinClusterRequest": { + "type": "object", + "required": [ + "clusterKey", + "leaderIp", + "nodeId", + "nodeIp" + ], + "properties": { + "clusterKey": { + "type": "string" + }, + "leaderIp": { + "type": "string" + }, + "nodeId": { + "type": "string" + }, + "nodeIp": { + "type": "string" + } + } + }, + "internal_handlers_cluster.NoteRequest": { "type": "object", "required": [ "content", @@ -11933,35 +13349,6 @@ } } }, - "internal_handlers_cluster.JoinClusterRequest": { - "type": "object", - "required": [ - "clusterKey", - "leaderApi", - "nodeId", - "nodeIp", - "nodePort" - ], - "properties": { - "clusterKey": { - "type": "string" - }, - "leaderApi": { - "type": "string" - }, - "nodeId": { - "type": "string" - }, - "nodeIp": { - "type": "string" - }, - "nodePort": { - "type": "integer", - "maximum": 65535, - "minimum": 1024 - } - } - }, "internal_handlers_cluster.RemovePeerRequest": { "type": "object", "required": [ @@ -12096,6 +13483,17 @@ } } }, + "internal_handlers_jail.ModifyAllowedOptionsRequest": { + "type": "object", + "properties": { + "allowedOptions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "internal_handlers_jail.ModifyBootOrderRequest": { "type": "object", "properties": { @@ -12123,6 +13521,14 @@ } } }, + "internal_handlers_jail.ModifyLifecycleHooksRequest": { + "type": "object", + "properties": { + "hooks": { + "$ref": "#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.Hooks" + } + } + }, "internal_handlers_jail.ModifyMetadataRequest": { "type": "object", "properties": { @@ -12134,6 +13540,14 @@ } } }, + "internal_handlers_jail.ModifyResolvConfRequest": { + "type": "object", + "properties": { + "resolvConf": { + "type": "string" + } + } + }, "internal_handlers_jail.SetInheritanceRequest": { "type": "object", "properties": { @@ -12509,6 +13923,9 @@ }, "metadata": { "type": "string" + }, + "networkConfig": { + "type": "string" } } }, @@ -12534,6 +13951,14 @@ } } }, + "internal_handlers_vm.ModifyQemuGuestAgentRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "internal_handlers_vm.ModifyRAMRequest": { "type": "object", "required": [ @@ -12561,6 +13986,14 @@ } } }, + "internal_handlers_vm.ModifyTPMRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, "internal_handlers_vm.ModifyWakeOnLanRequest": { "type": "object", "properties": { diff --git a/web/static/swagger/swagger.yaml b/web/static/swagger/swagger.yaml index 35209f7f..dd365f16 100644 --- a/web/static/swagger/swagger.yaml +++ b/web/static/swagger/swagger.yaml @@ -192,6 +192,19 @@ definitions: status: type: string type: object + ? github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate + : properties: + data: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate' + type: array + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_Downloads: properties: data: @@ -458,6 +471,17 @@ definitions: status: type: string type: object + github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList: + properties: + data: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.SimpleList' + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_State: properties: data: @@ -480,6 +504,17 @@ definitions: status: type: string type: object + github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList: + properties: + data: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList' + error: + type: string + message: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_network_Leases: properties: data: @@ -661,6 +696,10 @@ definitions: type: string email: type: string + groups: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group' + type: array id: type: integer lastLoginTime: @@ -707,6 +746,10 @@ definitions: type: integer diskUsage: type: number + guestIDs: + items: + type: integer + type: array hostname: type: string id: @@ -854,8 +897,14 @@ definitions: items: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.Network' type: array + resolvConf: + type: string resourceLimits: type: boolean + snapshots: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot' + type: array startAtBoot: type: boolean startLogs: @@ -897,6 +946,8 @@ definitions: properties: enabled: type: boolean + id: + type: integer jid: type: integer phase: @@ -904,6 +955,29 @@ definitions: script: type: string type: object + github_com_alchemillahq_sylve_internal_db_models_jail.JailSnapshot: + properties: + createdAt: + type: string + ctId: + type: integer + description: + type: string + id: + type: integer + jid: + type: integer + name: + type: string + parentSnapshotId: + type: integer + rootDataset: + type: string + snapshotName: + type: string + updatedAt: + type: string + type: object github_com_alchemillahq_sylve_internal_db_models_jail.JailStats: properties: cpuUsage: @@ -955,10 +1029,14 @@ definitions: type: integer macObj: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object' + manualSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch' name: type: string slaac: type: boolean + standardSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch' switchId: type: integer switchType: @@ -1079,6 +1157,9 @@ definitions: type: integer isUsed: type: boolean + isUsedBy: + description: '"", "dhcp" for now' + type: string name: type: string resolutions: @@ -1247,6 +1328,23 @@ definitions: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.Group' type: array type: object + github_com_alchemillahq_sylve_internal_db_models_utilities.CloudInitTemplate: + properties: + createdAt: + type: string + id: + type: integer + meta: + type: string + name: + type: string + networkConfig: + type: string + updatedAt: + type: string + user: + type: string + type: object github_com_alchemillahq_sylve_internal_db_models_utilities.DownloadStatus: enum: - pending @@ -1345,6 +1443,10 @@ definitions: type: integer macObj: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.Object' + manualSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.ManualSwitch' + standardSwitch: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_network.StandardSwitch' switchId: type: integer switchType: @@ -1399,6 +1501,8 @@ definitions: type: string cloudInitMetaData: type: string + cloudInitNetworkConfig: + type: string cpuCores: type: integer cpuPinning: @@ -1427,6 +1531,8 @@ definitions: items: type: integer type: array + qemuGuestAgent: + type: boolean ram: type: integer rid: @@ -1435,6 +1541,10 @@ definitions: type: boolean shutdownWaitTime: type: integer + snapshots: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot' + type: array startAtBoot: type: boolean startOrder: @@ -1481,6 +1591,31 @@ definitions: vmId: type: integer type: object + github_com_alchemillahq_sylve_internal_db_models_vm.VMSnapshot: + properties: + createdAt: + type: string + description: + type: string + id: + type: integer + name: + type: string + parentSnapshotId: + type: integer + rid: + type: integer + rootDatasets: + items: + type: string + type: array + snapshotName: + type: string + updatedAt: + type: string + vmId: + type: integer + type: object github_com_alchemillahq_sylve_internal_db_models_vm.VMStats: properties: cpuUsage: @@ -1588,6 +1723,33 @@ definitions: partial: type: boolean type: object + github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync: + properties: + api: + type: string + cpu: + type: integer + cpuUsage: + type: number + disk: + type: integer + diskUsage: + type: number + guestIds: + items: + type: integer + type: array + hostname: + type: string + memory: + type: integer + memoryUsage: + type: number + nodeUuid: + type: string + status: + type: string + type: object github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeResources: properties: hostname: @@ -1607,6 +1769,10 @@ definitions: properties: address: type: string + guestIDs: + items: + type: integer + type: array id: type: string isLeader: @@ -1651,6 +1817,30 @@ definitions: uuid: type: string type: object + github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture: + enum: + - amd64 + - "386" + - arm + - arm64 + - riscv64 + - ppc64 + - ppc64le + - s390x + - wasm + - loong64 + type: string + x-enum-varnames: + - ArchAMD64 + - Arch386 + - ArchARM + - ArchARM64 + - ArchRISCV64 + - ArchPPC64 + - ArchPPC64LE + - ArchS390X + - ArchWASM + - ArchLOONG64 github_com_alchemillahq_sylve_internal_interfaces_services_info.BasicInfo: properties: bootMode: @@ -1668,6 +1858,8 @@ definitions: type: object github_com_alchemillahq_sylve_internal_interfaces_services_info.CPUInfo: properties: + architecture: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_info.Architecture' cache: properties: l1d: @@ -1812,6 +2004,8 @@ definitions: type: string pool: type: string + resolvConf: + type: string resourceLimits: type: boolean slaac: @@ -1830,6 +2024,35 @@ definitions: - pool - type type: object + github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest: + properties: + defaultGateway: + type: boolean + dhcp: + type: boolean + ip4: + type: integer + ip4gw: + type: integer + ip6: + type: integer + ip6gw: + type: integer + macId: + type: integer + name: + type: string + networkId: + type: integer + slaac: + type: boolean + switchName: + type: string + required: + - name + - networkId + - switchName + type: object github_com_alchemillahq_sylve_internal_interfaces_services_jail.HookPhase: properties: enabled: @@ -1900,6 +2123,8 @@ definitions: type: string cloudInitMetaData: type: string + cloudInitNetworkConfig: + type: string cpuCores: type: integer cpuPinning: @@ -1924,6 +2149,8 @@ definitions: items: type: integer type: array + qemuGuestAgent: + type: boolean ram: type: integer rid: @@ -2015,6 +2242,10 @@ definitions: type: object github_com_alchemillahq_sylve_internal_interfaces_services_libvirt.SimpleList: properties: + cpuPinning: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models_vm.VMCPUPinning' + type: array id: type: integer name: @@ -2225,9 +2456,38 @@ definitions: $ref: '#/definitions/github_com_alchemillahq_sylve_internal_db_models.AvailableService' type: array required: - - pools - services type: object + github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest: + properties: + meta: + type: string + name: + maxLength: 255 + minLength: 1 + type: string + networkConfig: + type: string + user: + type: string + required: + - meta + - name + - user + type: object + github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest: + properties: + meta: + type: string + name: + maxLength: 255 + minLength: 1 + type: string + networkConfig: + type: string + user: + type: string + type: object github_com_alchemillahq_sylve_internal_interfaces_services_utilities.UTypeGroupedDownload: properties: label: @@ -2292,6 +2552,7 @@ definitions: type: array required: - name + - raidType type: object github_com_alchemillahq_sylve_internal_interfaces_services_zfs.EditVolumeRequest: properties: @@ -2960,17 +3221,28 @@ definitions: type: string nodeIp: type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer required: - clusterKey - nodeId - nodeIp - - nodePort type: object - internal_handlers_cluster.CreateNoteRequest: + internal_handlers_cluster.JoinClusterRequest: + properties: + clusterKey: + type: string + leaderIp: + type: string + nodeId: + type: string + nodeIp: + type: string + required: + - clusterKey + - leaderIp + - nodeId + - nodeIp + type: object + internal_handlers_cluster.NoteRequest: properties: content: minLength: 3 @@ -2982,27 +3254,6 @@ definitions: - content - title type: object - internal_handlers_cluster.JoinClusterRequest: - properties: - clusterKey: - type: string - leaderApi: - type: string - nodeId: - type: string - nodeIp: - type: string - nodePort: - maximum: 65535 - minimum: 1024 - type: integer - required: - - clusterKey - - leaderApi - - nodeId - - nodeIp - - nodePort - type: object internal_handlers_cluster.RemovePeerRequest: properties: nodeId: @@ -3091,6 +3342,13 @@ definitions: additionalOptions: type: string type: object + internal_handlers_jail.ModifyAllowedOptionsRequest: + properties: + allowedOptions: + items: + type: string + type: array + type: object internal_handlers_jail.ModifyBootOrderRequest: properties: bootOrder: @@ -3108,6 +3366,11 @@ definitions: fstab: type: string type: object + internal_handlers_jail.ModifyLifecycleHooksRequest: + properties: + hooks: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.Hooks' + type: object internal_handlers_jail.ModifyMetadataRequest: properties: env: @@ -3115,6 +3378,11 @@ definitions: metadata: type: string type: object + internal_handlers_jail.ModifyResolvConfRequest: + properties: + resolvConf: + type: string + type: object internal_handlers_jail.SetInheritanceRequest: properties: ipv4: @@ -3364,6 +3632,8 @@ definitions: type: string metadata: type: string + networkConfig: + type: string type: object internal_handlers_vm.ModifyIgnoreUMSRsRequest: properties: @@ -3379,6 +3649,11 @@ definitions: required: - pciDevices type: object + internal_handlers_vm.ModifyQemuGuestAgentRequest: + properties: + enabled: + type: boolean + type: object internal_handlers_vm.ModifyRAMRequest: properties: ram: @@ -3396,6 +3671,11 @@ definitions: waitTime: type: integer type: object + internal_handlers_vm.ModifyTPMRequest: + properties: + enabled: + type: boolean + type: object internal_handlers_vm.ModifyWakeOnLanRequest: properties: enabled: @@ -3610,7 +3890,7 @@ info: url: https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE termsOfService: https://github.com/AlchemillaHQ/Sylve/blob/master/LICENSE title: Sylve API - version: 0.0.1 + version: 0.1.1 paths: /auth/groups: get: @@ -3699,6 +3979,38 @@ paths: summary: Delete Group tags: - Groups + /auth/groups/members: + put: + consumes: + - application/json + description: Update the members of a specified group + parameters: + - description: Update group members request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_auth.AddUsersToGroupRequest' + produces: + - application/json + responses: + "200": + description: Group members updated successfully + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update Group Members + tags: + - Groups /auth/groups/users: post: consumes: @@ -3971,7 +4283,7 @@ paths: post: consumes: - application/json - description: Create a cluster given a bootstrapping nodes IP and Port + description: Create a cluster given a bootstrapping node IP produces: - application/json responses: @@ -4155,6 +4467,32 @@ paths: summary: Get Cluster Resources (per node) tags: - Cluster + /cluster/resync-state: + post: + consumes: + - application/json + description: Replays current cluster-backed state through Raft and forces a + snapshot from the leader + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "409": + description: Conflict + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Resync Cluster State + tags: + - Cluster /disk/create-partitions: post: consumes: @@ -4536,6 +4874,27 @@ paths: summary: Get Historical Network information tags: - system + /info/node: + get: + consumes: + - application/json + description: Get the node information about the system (mainly for cluster stuff) + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_info_BasicInfo' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get Node Info + tags: + - Info /info/notes: get: consumes: @@ -4661,6 +5020,42 @@ paths: summary: Delete a Cluster Note tags: - Cluster + put: + consumes: + - application/json + description: Update an existing note in the cluster + parameters: + - description: Note ID + in: path + name: id + required: true + type: integer + - description: Update Note Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_cluster.NoteRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update a Cluster Note + tags: + - Cluster /info/notes/bulk-delete: post: consumes: @@ -4766,6 +5161,41 @@ paths: summary: Get Historical Swap information tags: - system + /intra-cluster/sync-health: + post: + consumes: + - application/json + description: Internal endpoint used by the Raft Leader to broadcast cluster + health to followers + parameters: + - description: Array of node health states + in: body + name: payload + required: true + schema: + items: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_cluster.NodeHealthSync' + type: array + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Sync Cluster Health + tags: + - Cluster Internal /jail: get: consumes: @@ -5080,6 +5510,33 @@ paths: summary: Add Network Switch to Jail tags: - Jail + put: + consumes: + - application/json + description: Edit a network switch on a jail + parameters: + - description: Edit Network Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_jail.EditJailNetworkRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Edit Network Switch for Jail + tags: + - Jail /jail/network/{ctId}/{networkId}: delete: consumes: @@ -5195,6 +5652,49 @@ paths: summary: List all Jails (Simple) tags: - Jail + /jail/simple/:id: + get: + consumes: + - application/json + description: Retrieve a simple jail by its CTID or ID + parameters: + - description: Jail CTID or ID + in: path + name: identifier + required: true + type: string + - default: ctid + description: Type of identifier (ctid or id) + enum: + - ctid + - id + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_jail_SimpleList' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get a Jail by CTID or ID (Simple) + tags: + - Jail /jail/state: get: consumes: @@ -5953,6 +6453,31 @@ paths: summary: Delete a Standard Switch tags: - Network + /network/update: + put: + consumes: + - application/json + description: Update a network interface attached to a virtual machine + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Update Network Switch for a Virtual Machine + tags: + - VM /options/additional-options/:rid: put: consumes: @@ -5985,6 +6510,38 @@ paths: summary: Modify Additional Options of a Jail tags: - Jail + /options/allowed-options/:rid: + put: + consumes: + - application/json + description: Modify allowed options configuration of a jail + parameters: + - description: Modify Allowed Options Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyAllowedOptionsRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify Allowed Options of a Jail + tags: + - Jail /options/boot-order/:rid: put: consumes: @@ -6177,6 +6734,38 @@ paths: summary: Modify Ignore UMSRs of a Virtual Machine tags: - VM + /options/lifecycle-hooks/:rid: + put: + consumes: + - application/json + description: Modify jail exec.* lifecycle hooks in one request + parameters: + - description: Modify Lifecycle Hooks Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyLifecycleHooksRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify Lifecycle Hooks of a Jail + tags: + - Jail /options/metadata/:rid: put: consumes: @@ -6209,6 +6798,70 @@ paths: summary: Modify Metadata of a Jail tags: - Jail + /options/qemu-guest-agent/:rid: + put: + consumes: + - application/json + description: Modify the QEMU Guest Agent configuration of a virtual machine + parameters: + - description: Modify QEMU Guest Agent Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_vm.ModifyQemuGuestAgentRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify QEMU Guest Agent of a Virtual Machine + tags: + - VM + /options/resolv-conf/:rid: + put: + consumes: + - application/json + description: Modify /etc/resolv.conf content for a jail + parameters: + - description: Modify resolv.conf Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_jail.ModifyResolvConfRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify resolv.conf of a Jail + tags: + - Jail /options/serial-console/:rid: put: consumes: @@ -6273,6 +6926,38 @@ paths: summary: Modify Shutdown Wait Time of a Virtual Machine tags: - VM + /options/tpm/:rid: + put: + consumes: + - application/json + description: Modify the TPM configuration of a virtual machine + parameters: + - description: Modify TPM Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_vm.ModifyTPMRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Modify TPM of a Virtual Machine + tags: + - VM /options/wol/:rid: put: consumes: @@ -6305,6 +6990,31 @@ paths: summary: Modify Wake-on-LAN of a Virtual Machine tags: - VM + /qga/:rid: + get: + consumes: + - application/json + description: Retrieve QEMU Guest Agent OS and network info of a virtual machine + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get QEMU Guest Agent info of a Virtual Machine + tags: + - VM /samba/audit-logs: get: consumes: @@ -7072,6 +7782,180 @@ paths: summary: Remove Passed Through Device tags: - System + /system/ppt-devices/import: + post: + consumes: + - application/json + description: Import an existing ppt device into Sylve passthrough management + parameters: + - description: Device ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_system.AddPassthroughDeviceRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Import Passed Through Device + tags: + - System + /system/ppt-devices/prepare: + post: + consumes: + - application/json + description: Add a device to loader.conf for passthrough on next boot + parameters: + - description: Device ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handlers_system.AddPassthroughDeviceRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Prepare Passed Through Device + tags: + - System + /utilities/cloud-init/templates: + get: + consumes: + - application/json + description: List all Cloud-Init templates + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-array_github_com_alchemillahq_sylve_internal_db_models_utilities_CloudInitTemplate' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: List Cloud-Init Templates + tags: + - Utilities + post: + consumes: + - application/json + description: Add a new Cloud-Init template + parameters: + - description: Add Template Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.AddTemplateRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Add Cloud-Init Template + tags: + - Utilities + /utilities/cloud-init/templates/:id: + delete: + consumes: + - application/json + description: Delete a Cloud-Init template by ID + parameters: + - description: Template ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Delete Cloud-Init Template + tags: + - Utilities + put: + consumes: + - application/json + description: Edit an existing Cloud-Init template + parameters: + - description: Edit Template Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal_interfaces_services_utilities.EditTemplateRequest' + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Edit Cloud-Init Template + tags: + - Utilities /utilities/downloads: get: consumes: @@ -7520,6 +8404,45 @@ paths: summary: List all VMs (Simple) tags: - VM + /vm/simple/:id: + get: + consumes: + - application/json + description: Retrieve a simple virtual machine object by its RID or ID + parameters: + - description: Virtual Machine RID or ID + in: path + name: id + required: true + type: string + - default: rid + description: Type of identifier (rid or id) + enum: + - rid + - id + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-github_com_alchemillahq_sylve_internal_interfaces_services_libvirt_SimpleList' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_alchemillahq_sylve_internal.APIResponse-any' + security: + - BearerAuth: [] + summary: Get a simple Virtual Machine by RID or ID + tags: + - VM /vm/stats/:rid/:step: get: consumes: