feat: add plugin registry and server operations

This commit is contained in:
2026-08-13 17:37:18 +07:00
parent 3f09e330be
commit 71a21b7b79
39 changed files with 2201 additions and 320 deletions
@@ -60,13 +60,16 @@ type JVMOptions struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=100
// +kubebuilder:default=80
// +kubebuilder:default=60
HeapPercent int32 `json:"heapPercent,omitempty"`
}
type MinecraftServerSpec struct {
Type ServerKind `json:"type"`
// +kubebuilder:default=true
Running *bool `json:"running,omitempty"`
// +optional
Description string `json:"description,omitempty"`
@@ -132,6 +132,11 @@ func (in *MinecraftServerList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MinecraftServerSpec) DeepCopyInto(out *MinecraftServerSpec) {
*out = *in
if in.Running != nil {
in, out := &in.Running, &out.Running
*out = new(bool)
**out = **in
}
out.Resources = in.Resources
out.JVM = in.JVM
in.Properties.DeepCopyInto(&out.Properties)
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.0
controller-gen.kubebuilder.io/version: v0.21.0
name: minecraftservers.minikura.kirameki.cafe
spec:
group: minikura.kirameki.cafe
@@ -220,7 +220,7 @@ spec:
jvm:
properties:
heapPercent:
default: 80
default: 60
format: int32
maximum: 100
minimum: 1
@@ -300,6 +300,9 @@ spec:
minimum: 256
type: integer
type: object
running:
default: true
type: boolean
serviceType:
default: ClusterIP
enum:
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.0
controller-gen.kubebuilder.io/version: v0.21.0
name: reverseproxyservers.minikura.kirameki.cafe
spec:
group: minikura.kirameki.cafe
+1 -1
View File
@@ -15,7 +15,7 @@ const (
MinecraftImage = "itzg/minecraft-server"
ProxyImage = "itzg/mc-proxy:latest"
ContainerPort = 25565
DefaultHeapPercent = 80
DefaultHeapPercent = 60
DefaultMemoryLimitMB = 2048
MinHeapMB = 256
)
+3 -3
View File
@@ -16,12 +16,12 @@ func TestHeapMB(t *testing.T) {
heapPercent int32
want string
}{
{"default percent when unset", 2048, 0, "1638M"},
{"default percent when unset", 2048, 0, "1228M"},
{"explicit percent", 2048, 50, "1024M"},
{"out of range falls back", 1024, 150, "819M"},
{"out of range falls back", 1024, 150, "614M"},
{"floor applies to tiny limits", 128, 80, "256M"},
{"full allocation", 1000, 100, "1000M"},
{"zero limit uses default memory", 0, 80, "1638M"},
{"zero limit uses default memory", 0, 60, "1228M"},
}
for _, tt := range tests {
+16 -4
View File
@@ -113,8 +113,12 @@ func minecraftPodSpec(mc *v1alpha1.MinecraftServer, stateful bool) corev1.PodSpe
return corev1.PodSpec{
Containers: []corev1.Container{{
Name: "minecraft",
Image: MinecraftImage,
Name: "minecraft",
Image: MinecraftImage,
Command: []string{"/bin/sh", "-c"},
Args: []string{
"rm -f /tmp/minikura-console && mkfifo /tmp/minikura-console && exec 3<>/tmp/minikura-console && exec /start <&3",
},
Ports: []corev1.ContainerPort{{
Name: "minecraft",
ContainerPort: ContainerPort,
@@ -132,10 +136,14 @@ func MinecraftDeployment(mc *v1alpha1.MinecraftServer) *appsv1.Deployment {
name := ServerName(mc.Name)
labels := ServerLabels(mc)
replicas := int32(1)
if mc.Spec.Running != nil && !*mc.Spec.Running {
replicas = 0
}
return &appsv1.Deployment{
ObjectMeta: ObjectMeta(name, mc.Namespace, labels),
Spec: appsv1.DeploymentSpec{
Replicas: ptr(int32(1)),
Replicas: ptr(replicas),
Selector: &metav1.LabelSelector{MatchLabels: SelectorLabels(name)},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
@@ -158,11 +166,15 @@ func MinecraftStatefulSet(mc *v1alpha1.MinecraftServer) (*appsv1.StatefulSet, er
return nil, fmt.Errorf("invalid storageSize %q: %w", size, err)
}
replicas := int32(1)
if mc.Spec.Running != nil && !*mc.Spec.Running {
replicas = 0
}
return &appsv1.StatefulSet{
ObjectMeta: ObjectMeta(name, mc.Namespace, labels),
Spec: appsv1.StatefulSetSpec{
ServiceName: name,
Replicas: ptr(int32(1)),
Replicas: ptr(replicas),
Selector: &metav1.LabelSelector{MatchLabels: SelectorLabels(name)},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
@@ -153,6 +153,37 @@ func TestStatelessHasNoDataVolume(t *testing.T) {
}
}
func TestMinecraftUsesPersistentConsolePipe(t *testing.T) {
podSpec := minecraftPodSpec(testServer(), true)
container := podSpec.Containers[0]
if len(container.Command) != 2 || container.Command[0] != "/bin/sh" {
t.Fatalf("command = %v, want shell wrapper", container.Command)
}
if len(container.Args) != 1 || container.Args[0] != "rm -f /tmp/minikura-console && mkfifo /tmp/minikura-console && exec 3<>/tmp/minikura-console && exec /start <&3" {
t.Fatalf("args = %v, want persistent console pipe", container.Args)
}
}
func TestStoppedServerHasZeroReplicas(t *testing.T) {
mc := testServer()
stopped := false
mc.Spec.Running = &stopped
dep := MinecraftDeployment(mc)
if dep.Spec.Replicas == nil || *dep.Spec.Replicas != 0 {
t.Fatalf("deployment replicas = %v, want 0", dep.Spec.Replicas)
}
sts, err := MinecraftStatefulSet(mc)
if err != nil {
t.Fatal(err)
}
if sts.Spec.Replicas == nil || *sts.Spec.Replicas != 0 {
t.Fatalf("statefulset replicas = %v, want 0", sts.Spec.Replicas)
}
}
func TestMinecraftConfigMap(t *testing.T) {
cm := MinecraftConfigMap(testServer())
if cm.Name != "minecraft-smp-config" {