| name | clicd-virtualization-panel |
| description | Use CLICD to manage LXC/KVM virtualization with web console, NAT/IPv6 networking, WebSSH/VNC, resource controls, and security monitoring. |
| triggers | ["set up CLICD virtualization panel","manage LXC containers with CLICD","configure CLICD NAT networking","use CLICD REST API","create KVM virtual machines in CLICD","configure CLICD security alerts","manage CLICD user permissions","integrate CLICD with billing system"] |
CLICD Virtualization Panel Skill
Skill by ara.so — Devtools Skills collection.
CLICD is a lightweight virtualization management panel for LXC and KVM that provides a web console, CLI tools, REST API, NAT/IPv6 networking, WebSSH/WebVNC access, resource quotas, traffic limits, snapshots, delegated sub-user access, and security monitoring. Built with Go backend and React frontend.
Installation
One-Click Install
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh -s -- uninstall
Prerequisites
- Linux system with systemd
- LXC or KVM/QEMU installed
- Root or sudo access
- iptables and conntrack-tools
Post-Installation
After installation, CLICD runs as a systemd service:
sudo systemctl status clicd
sudo journalctl -u clicd -f
sudo systemctl restart clicd
Access the web interface at http://your-server-ip:8080 (default port).
Configuration
Main Configuration File
Configuration is typically stored in /etc/clicd/config.yaml or similar location:
server:
host: "0.0.0.0"
port: 8080
tls:
enabled: false
cert_file: ""
key_file: ""
letsencrypt: false
domain: ""
database:
type: "sqlite"
path: "/var/lib/clicd/clicd.db"
lxc:
enabled: true
default_storage_pool: "default"
default_network: "lxcbr0"
kvm:
enabled: true
default_storage_pool: "default"
default_network: "virbr0"
networking:
nat4:
enabled: true
public_ip: "1.2.3.4"
port_range_start: 10000
port_range_end: 60000
ipv6:
enabled: true
prefix: "2001:db8::/48"
auto_detect: true
security:
alerts_enabled: true
conntrack_monitoring:
Environment Variables
export CLICD_CONFIG="/path/to/config.yaml"
export CLICD_ADMIN_PASSWORD="your-secure-password"
export CLICD_DB_PATH="/var/lib/clicd/clicd.db"
export CLICD_LOG_LEVEL="info"
CLI Usage
CLICD provides a CLI interface for common operations:
Container Management
clicd container list
clicd container create \
--name web-server-01 \
--type lxc \
--template ubuntu-22.04 \
--cpu 2 \
--memory 2048 \
--disk 20 \
--ipv4 nat \
--ipv6 auto
clicd container start web-server-01
clicd container stop web-server-01
clicd container delete web-server-01 --force
clicd container password web-server-01 --password "NewSecurePass123"
clicd container batch-start --ids "1,2,3,4,5"
clicd container batch-stop --pattern "test-*"
Image Management
clicd image list
clicd image download ubuntu-22.04-lxc
clicd image enable ubuntu-22.04-lxc
clicd image disable centos-7-lxc
clicd image cache-clear
Networking
clicd nat list
clicd nat add \
--container web-server-01 \
--protocol tcp \
--public-port 8080 \
--private-port 80
clicd nat remove --id 123
clicd ipv6 status
clicd ipv6 assign --container web-server-01
User Management
clicd user create \
--username client01 \
--password "SecurePass123" \
--containers "web-server-01,db-server-01"
clicd user link --username client01
clicd user list
clicd user update --username client01 --add-container api-server-01
REST API
All API endpoints are versioned under /api/v1. Authentication uses API keys or session tokens.
Authentication
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResponse struct {
Token string `json:"token"`
Message string `json:"message"`
}
func login() (string, error) {
apiURL := os.Getenv("CLICD_API_URL")
reqBody := LoginRequest{
Username: os.Getenv("CLICD_USERNAME"),
Password: os.Getenv("CLICD_PASSWORD"),
}
jsonData, _ := json.Marshal(reqBody)
resp, err := http.Post(
apiURL+"/api/v1/auth/login",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return "", err
}
defer resp.Body.Close()
var loginResp LoginResponse
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &loginResp)
return loginResp.Token, nil
}
Container Operations
type Container struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
CPU int `json:"cpu"`
Memory int `json:"memory"`
Disk int `json:"disk"`
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
ExpiryDate string `json:"expiry_date"`
}
type CreateContainerRequest struct {
Name string `json:"name"`
Type string `json:"type"`
Template string `json:"template"`
CPU int `json:"cpu"`
Memory int `json:"memory"`
Disk int `json:"disk"`
Password string `json:"password"`
IPv4Type string `json:"ipv4_type"`
IPv6Enable bool `json:"ipv6_enable"`
ExpiryDays int `json:"expiry_days"`
}
(*Container, ) {
apiURL := os.Getenv()
jsonData, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
,
apiURL+,
bytes.NewBuffer(jsonData),
)
httpReq.Header.Set(, +token)
httpReq.Header.Set(, )
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
, err
}
resp.Body.Close()
container Container
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &container)
&container,
}
([]Container, ) {
apiURL := os.Getenv()
httpReq, _ := http.NewRequest(, apiURL+, )
httpReq.Header.Set(, +token)
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
, err
}
resp.Body.Close()
containers []Container
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &containers)
containers,
}
{
apiURL := os.Getenv()
httpReq, _ := http.NewRequest(
,
fmt.Sprintf(, apiURL, containerID),
,
)
httpReq.Header.Set(, +token)
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
err
}
resp.Body.Close()
}
{
apiURL := os.Getenv()
url := fmt.Sprintf(, apiURL, containerID)
force {
url +=
}
httpReq, _ := http.NewRequest(, url, )
httpReq.Header.Set(, +token)
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
err
}
resp.Body.Close()
}
Batch Operations
type BatchActionRequest struct {
IDs []int `json:"ids"`
Action string `json:"action"`
}
type BatchCreateRequest struct {
Count int `json:"count"`
NamePrefix string `json:"name_prefix"`
Template string `json:"template"`
CPU int `json:"cpu"`
Memory int `json:"memory"`
Disk int `json:"disk"`
}
func batchAction(token string, req BatchActionRequest) error {
apiURL := os.Getenv("CLICD_API_URL")
jsonData, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
"POST",
apiURL+"/api/v1/containers/batch",
bytes.NewBuffer(jsonData),
)
httpReq.Header.Set("Authorization", "Bearer "+token)
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func batchCreate(token string, req BatchCreateRequest) ([]Container, error) {
apiURL := os.Getenv()
jsonData, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
,
apiURL+,
bytes.NewBuffer(jsonData),
)
httpReq.Header.Set(, +token)
httpReq.Header.Set(, )
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
, err
}
resp.Body.Close()
containers []Container
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &containers)
containers,
}
Networking API
type NATMapping struct {
ID int `json:"id"`
ContainerID int `json:"container_id"`
Protocol string `json:"protocol"`
PublicPort int `json:"public_port"`
PrivatePort int `json:"private_port"`
}
type CreateNATRequest struct {
ContainerID int `json:"container_id"`
Protocol string `json:"protocol"`
PrivatePort int `json:"private_port"`
PublicPort int `json:"public_port,omitempty"`
}
func createNATMapping(token string, req CreateNATRequest) (*NATMapping, error) {
apiURL := os.Getenv("CLICD_API_URL")
jsonData, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
"POST",
apiURL+"/api/v1/nat",
bytes.NewBuffer(jsonData),
)
httpReq.Header.Set("Authorization", "Bearer "+token)
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var mapping NATMapping
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &mapping)
&mapping,
}
(, ) {
apiURL := os.Getenv()
httpReq, _ := http.NewRequest(
,
fmt.Sprintf(, apiURL, containerID),
,
)
httpReq.Header.Set(, +token)
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
, err
}
resp.Body.Close()
result {
IPv6
}
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &result)
result.IPv6,
}
Security Monitoring
type SecurityAlert struct {
ID int `json:"id"`
ContainerID int `json:"container_id"`
Type string `json:"type"`
Severity string `json:"severity"`
Description string `json:"description"`
Timestamp string `json:"timestamp"`
}
func getSecurityAlerts(token string, containerID int) ([]SecurityAlert, error) {
apiURL := os.Getenv("CLICD_API_URL")
url := fmt.Sprintf("%s/api/v1/security/alerts", apiURL)
if containerID > 0 {
url += fmt.Sprintf("?container_id=%d", containerID)
}
httpReq, _ := http.NewRequest("GET", url, nil)
httpReq.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var alerts []SecurityAlert
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &alerts)
return alerts, nil
}
Snapshots
type Snapshot struct {
ID int `json:"id"`
ContainerID int `json:"container_id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt string `json:"created_at"`
Size int64 `json:"size"`
}
type CreateSnapshotRequest struct {
ContainerID int `json:"container_id"`
Name string `json:"name"`
Description string `json:"description"`
}
func createSnapshot(token string, req CreateSnapshotRequest) (*Snapshot, error) {
apiURL := os.Getenv("CLICD_API_URL")
jsonData, _ := json.Marshal(req)
httpReq, _ := http.NewRequest(
"POST",
apiURL+"/api/v1/snapshots",
bytes.NewBuffer(jsonData),
)
httpReq.Header.Set("Authorization", "Bearer "+token)
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var snapshot Snapshot
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &snapshot)
return &snapshot, nil
}
{
apiURL := os.Getenv()
httpReq, _ := http.NewRequest(
,
fmt.Sprintf(, apiURL, snapshotID),
,
)
httpReq.Header.Set(, +token)
client := &http.Client{}
resp, err := client.Do(httpReq)
err != {
err
}
resp.Body.Close()
}
Common Patterns
Complete Container Provisioning Workflow
package main
import (
"fmt"
"log"
"os"
)
func provisionContainer() {
token, err := login()
if err != nil {
log.Fatal("Login failed:", err)
}
container, err := createContainer(token, CreateContainerRequest{
Name: "web-app-01",
Type: "lxc",
Template: "ubuntu-22.04",
CPU: 4,
Memory: 4096,
Disk: 50,
Password: os.Getenv("CONTAINER_PASSWORD"),
IPv4Type: "nat",
IPv6Enable: true,
ExpiryDays: 30,
})
if err != nil {
log.Fatal("Container creation failed:", err)
}
fmt.Printf("Container created: ID=%d, Name=%s\n", container.ID, container.Name)
httpMapping, _ := createNATMapping(token, CreateNATRequest{
ContainerID: container.ID,
Protocol: "tcp",
PrivatePort: 80,
PublicPort: 0,
})
fmt.Printf("HTTP port mapping: %d -> 80\n", httpMapping.PublicPort)
httpsMapping, _ := createNATMapping(token, CreateNATRequest{
ContainerID: container.ID,
Protocol: "tcp",
PrivatePort: 443,
PublicPort: 0,
})
fmt.Printf("HTTPS port mapping: %d -> 443\n", httpsMapping.PublicPort)
ipv6, _ := assignIPv6(token, container.ID)
fmt.Printf(, ipv6)
startContainer(token, container.ID)
fmt.Println()
snapshot, _ := createSnapshot(token, CreateSnapshotRequest{
ContainerID: container.ID,
Name: ,
Description: ,
})
fmt.Printf(, snapshot.Name)
}
Batch Container Deployment
func deployMultipleContainers(count int, namePrefix string) {
token, _ := login()
containers, err := batchCreate(token, BatchCreateRequest{
Count: count,
NamePrefix: namePrefix,
Template: "debian-12",
CPU: 2,
Memory: 2048,
Disk: 20,
})
if err != nil {
log.Fatal("Batch create failed:", err)
}
fmt.Printf("Created %d containers\n", len(containers))
for _, container := range containers {
assignIPv6(token, container.ID)
createNATMapping(token, CreateNATRequest{
ContainerID: container.ID,
Protocol: "tcp",
PrivatePort: 22,
})
fmt.Printf("Configured container: %s\n", container.Name)
}
var ids []int
for _, c := range containers {
ids = append(ids, c.ID)
}
batchAction(token, BatchActionRequest{
IDs: ids,
Action: "start",
})
fmt.Println("All containers started")
}
Security Monitoring Dashboard
func monitorSecurity(token string) {
containers, _ := listContainers(token)
for _, container := range containers {
alerts, err := getSecurityAlerts(token, container.ID)
if err != nil {
continue
}
if len(alerts) > 0 {
fmt.Printf("\n=== Container: %s (ID: %d) ===\n", container.Name, container.ID)
for _, alert := range alerts {
fmt.Printf("[%s] %s: %s\n",
alert.Severity,
alert.Type,
alert.Description,
)
}
}
}
}
Auto-Scaling Based on Load
type ContainerStats struct {
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage int64 `json:"memory_usage"`
DiskUsage int64 `json:"disk_usage"`
}
func autoScale(token string, poolName string, maxInstances int) {
containers, _ := listContainers(token)
var poolContainers []Container
for _, c := range containers {
if c.Name[:len(poolName)] == poolName {
poolContainers = append(poolContainers, c)
}
}
totalCPU := 0.0
for _, c := range poolContainers {
stats := getContainerStats(token, c.ID)
totalCPU += stats.CPUUsage
}
avgCPU := totalCPU / float64(len(poolContainers))
if avgCPU > 70.0 && len(poolContainers) < maxInstances {
fmt.Println("Scaling up...")
createContainer(token, CreateContainerRequest{
Name: fmt.Sprintf("%s-%d", poolName, len(poolContainers)+1),
Type: "lxc",
Template: "ubuntu-22.04",
CPU: 2,
Memory: 2048,
Disk: 20,
})
}
avgCPU < && (poolContainers) > {
fmt.Println()
lastContainer := poolContainers[(poolContainers)]
stopContainer(token, lastContainer.ID)
deleteContainer(token, lastContainer.ID, )
}
}
Troubleshooting
Service Won't Start
sudo systemctl status clicd
sudo journalctl -u clicd --no-pager
clicd config validate
Container Creation Fails
lxc-checkconfig
lxc storage list
ip addr show lxcbr0
sudo lxc-create -n test -t download -- -d ubuntu -r jammy -a amd64
sudo lxc-start -n test
export CLICD_LOG_LEVEL=debug
sudo systemctl restart clicd
NAT Port Forwarding Not Working
sudo iptables -t nat -L -n -v
sysctl net.ipv4.ip_forward
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
clicd nat check-config
sudo iptables -t nat -A PREROUTING -p tcp --dport 10080 -j DNAT --to-destination 10.0.3.100:80
sudo iptables -t nat -A POSTROUTING -s 10.0.3.0/24 -j MASQUERADE
IPv6 Not Working
sysctl net.ipv6.conf.all.forwarding
sudo sysctl -w net.ipv6.conf.all.forwarding=1
clicd ipv6 detect-prefix
lxc-attach -n container-name -- ip -6 addr show
API Authentication Fails
func debugAuth() {
token, err := login()
if err != nil {
log.Fatal("Login error:", err)
}
fmt.Printf("Token: %s\n", token)
httpReq, _ := http.NewRequest("GET", os.Getenv("CLICD_API_URL")+"/api/v1/containers", nil)
httpReq.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
log.Fatal("Request error:", err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Response: %s\n", body)
}
Database Locked Errors
ps aux | grep clicd
sudo systemctl stop clicd
sqlite3 /var/lib/clicd/clicd.db "PRAGMA integrity_check;"
sudo cp /var/lib/clicd/clicd.db /var/lib/clicd/clicd.db.backup
sudo rm /var/lib/clicd/clicd.db
sudo systemctl start clicd
High Memory Usage
clicd container list --show-usage
clicd container update --id 123 --memory 1024
sudo update-grub
sudo reboot
watch -n 1 'free -h && echo && lxc-ls -f'
Security Alerts Not Working
lsmod | grep nf_conntrack
sudo modprobe nf_conntrack
which conntrack
sudo conntrack -L
sudo systemctl restart clicd
WebSSH/VNC Not Connecting
clicd container status --id 123
lxc-attach -n container-name -- systemctl status ssh
lxc-attach -n container-name -- apt install tigervnc-standalone-server
vncviewer container-ip:5900
Integration Examples
Billing System Integration (Mofang)
CLICD includes a