Compare commits

..

7 commits

Author SHA1 Message Date
bb60a21050 Merge pull request 'Support running insde kubernetes' (#5) from pr-kube-yamls into main
All checks were successful
/ build (push) Successful in 6m10s
Reviewed-on: #5
2026-06-03 23:29:40 +02:00
8da0f78696 feat: handle internall address differntly if running inside a cluster 2026-06-03 23:27:42 +02:00
876b568a54 Merge branch 'main' into pr-kube-yamls 2026-06-03 15:58:16 +02:00
c4395ddc10 fix: Role permission groups and service label matcher 2026-06-03 15:57:47 +02:00
2244bc6c0d wip 3 2026-06-03 15:44:42 +02:00
a8311bb8ca wip #2 2026-06-03 11:37:17 +02:00
80b264331e wip 2026-05-29 17:40:33 +02:00
3 changed files with 128 additions and 25 deletions

68
kube/roles.yaml Normal file
View file

@ -0,0 +1,68 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: minecraft-ingress
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: minecraft-ingress
rules:
- apiGroups: ["apps", ""] # "" indicates the core API group
resources: ["pods","deployments","services"]
verbs: ["get", "list", "patch", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: minecraft-ingress
namespace: default
subjects:
- kind: ServiceAccount
name: minecraft-ingress
namespace: default
roleRef:
kind: Role
name: minecraft-ingress
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minecraft-ingress
labels:
app: minecraft-ingress
spec:
replicas: 1
selector:
matchLabels:
app: minecraft-ingress
template:
metadata:
labels:
app: minecraft-ingress
spec:
serviceAccountName: minecraft-ingress
containers:
- name: minecraft-ingress
image: git.tami.moe/tamipes/minecraft-ingress:latest
env:
- name: FILTER_CONN
value: '(addr == "87.229.85.222") || (addr == "") || (addr == "ogmur.xyz") || (addr == "@mat:matdoes.dev (hi honeypots) ") || (addr == "@mat:matdoes.dev ") || (addr == "slowstack.tv")'
---
apiVersion: v1
kind: Service
metadata:
name: minecraft-ingress
labels:
app: minecraft-ingress
spec:
ports:
- name: minecraft-ingress
port: 25565
nodePort: 30565
selector:
app: minecraft-ingress
type: NodePort

View file

@ -1,6 +1,9 @@
use std::{collections::HashMap, fmt, sync::Arc, time::Duration}; use std::{collections::HashMap, fmt, sync::Arc, time::Duration};
use k8s_openapi::api::{apps::v1::Deployment, core::v1::Service}; use k8s_openapi::{
api::{apps::v1::Deployment, core::v1::Service},
apimachinery::pkg::util::intstr::IntOrString,
};
use kube::{ use kube::{
api::{ListParams, ObjectList, Patch, PatchParams}, api::{ListParams, ObjectList, Patch, PatchParams},
runtime::reflector::Lookup, runtime::reflector::Lookup,
@ -25,11 +28,16 @@ use crate::{
pub struct KubeCache { pub struct KubeCache {
deployments: Api<Deployment>, deployments: Api<Deployment>,
services: Api<Service>, services: Api<Service>,
in_cluster: bool,
} }
impl KubeCache { impl KubeCache {
/// This initializes the creation of a "kubernetes client" /// This initializes the creation of a "kubernetes client"
/// and if it is not possible returns a None. /// and if it is not possible returns a None.
pub async fn create() -> Option<KubeCache> { pub async fn create() -> Option<KubeCache> {
let in_cluster = match std::env::var("KUBERNETES_SERVICE_HOST") {
Ok(x) => true,
Err(e) => false,
};
let client = Client::try_default().await.unwrap(); let client = Client::try_default().await.unwrap();
let deployments: Api<Deployment> = Api::default_namespaced(client.clone()); let deployments: Api<Deployment> = Api::default_namespaced(client.clone());
@ -38,6 +46,7 @@ impl KubeCache {
return Some(KubeCache { return Some(KubeCache {
deployments, deployments,
services, services,
in_cluster,
}); });
} }
async fn get_dep(&self, name: &str) -> Result<Deployment, kube::Error> { async fn get_dep(&self, name: &str) -> Result<Deployment, kube::Error> {
@ -63,10 +72,17 @@ impl KubeCache {
Some(result.name()?.to_string()) Some(result.name()?.to_string())
} }
pub async fn query_srv_addr(&self, addr: &str, port: &str) -> Option<String> { pub async fn query_srv(&self, addr: &str, port: &str) -> Option<Service> {
let deploys = self.get_srvs().await; let deploys = self.get_srvs().await;
let result = deploys.iter().find(|x| filter_label_value(x, addr, port))?; let result = deploys.into_iter().find(|x| {
Some(result.name()?.to_string()) let in_cluster = match x.spec.as_ref().unwrap().type_.as_ref() {
Some(t) => t == "ClusterIP",
None => false,
};
let incorrect_type = in_cluster ^ self.in_cluster;
!incorrect_type && filter_label_value(&x, addr, port)
})?;
Some(result)
} }
async fn set_dep_scale(&self, name: &str, num: i32) -> Result<Deployment, kube::Error> { async fn set_dep_scale(&self, name: &str, num: i32) -> Result<Deployment, kube::Error> {
@ -100,11 +116,11 @@ impl MinecraftAPI<Server> for McApi {
)) ))
} }
}; };
let srv_name = match self.cache.query_srv_addr(&addr, &port).await { let service = match self.cache.query_srv(&addr, &port).await {
Some(x) => x, Some(x) => x,
None => { None => {
return Err(OpaqueError::create_with_kind( return Err(OpaqueError::create_with_kind(
"Failed to find service name by addr", "Failed to find service by addr and port",
"SrvNameLookupFailed", "SrvNameLookupFailed",
)) ))
} }
@ -116,20 +132,46 @@ impl MinecraftAPI<Server> for McApi {
x.to_string() x.to_string()
) )
})?; })?;
let service = self.cache.get_srv(&srv_name).await.map_err(|x| {
format!(
"Failed to query cache for service with dep_name err:{}",
x.to_string()
)
})?;
tracing::debug!("found kubernetes deployment & service"); tracing::debug!("found kubernetes deployment & service");
let service_port_spec = service.clone().spec.unwrap().ports.unwrap();
let port = service_port_spec
.iter()
.find(|x| x.name.clone().unwrap() == "mc-router")
.ok_or(OpaqueError::create(
"Could not find \"mc-router\" nodePort for server",
))?;
let port_string;
let inter_addr = match self.cache.in_cluster {
false => {
let node_port = port
.node_port
.map(|x| x.to_string())
.ok_or(OpaqueError::create("Could not map nodePort to port string"))?;
port_string = node_port.clone();
format!("localhost:{}", node_port)
}
true => {
let target_port = port.port;
let a = format!(
"{}.default.svc.cluster.local:{}",
service
.name()
.ok_or("Could not get name of ClusterIP service")?,
target_port
);
port_string = a.clone();
a
}
};
tracing::info!(inter_addr = inter_addr);
return Ok(Server { return Ok(Server {
dep: deployment, dep: deployment,
srv: service, srv: service,
server_addr: addr.to_string(), server_addr: addr.to_string(),
server_port: port.to_string(), server_port: port_string,
cache: self.cache.clone(), cache: self.cache.clone(),
inter_addr: inter_addr,
}); });
} }
@ -223,6 +265,7 @@ pub struct Server {
server_addr: String, server_addr: String,
server_port: String, server_port: String,
cache: KubeCache, cache: KubeCache,
inter_addr: String,
} }
impl fmt::Debug for Server { impl fmt::Debug for Server {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -308,8 +351,8 @@ impl MinecraftServerHandle for Server {
} }
} }
fn get_internal_address(&self) -> Option<String> { fn get_internal_address(&self) -> &str {
Some(format!("localhost:{}", self.get_internal_port()?)) self.inter_addr.as_str()
} }
fn get_addr(&self) -> Option<String> { fn get_addr(&self) -> Option<String> {
@ -358,11 +401,6 @@ impl MinecraftServerHandle for Server {
} }
} }
fn get_internal_port(&self) -> Option<String> {
let a = self.srv.clone().spec.unwrap().ports.unwrap();
let port = a.iter().find(|x| x.name.clone().unwrap() == "mc-router")?;
port.node_port.map(|x| x.to_string())
}
fn get_port(&self) -> Option<String> { fn get_port(&self) -> Option<String> {
Some(self.server_port.clone()) Some(self.server_port.clone())
} }

View file

@ -64,16 +64,13 @@ pub trait MinecraftServerHandle: Clone {
async fn start(&self) -> Result<(), OpaqueError>; async fn start(&self) -> Result<(), OpaqueError>;
async fn stop(&self) -> Result<(), OpaqueError>; async fn stop(&self) -> Result<(), OpaqueError>;
async fn query_status(&self) -> Result<ServerDeploymentStatus, OpaqueError>; async fn query_status(&self) -> Result<ServerDeploymentStatus, OpaqueError>;
fn get_internal_address(&self) -> Option<String>; fn get_internal_address(&self) -> &str;
fn get_internal_port(&self) -> Option<String>;
fn get_addr(&self) -> Option<String>; fn get_addr(&self) -> Option<String>;
fn get_port(&self) -> Option<String>; fn get_port(&self) -> Option<String>;
fn get_motd(&self) -> Option<String>; fn get_motd(&self) -> Option<String>;
async fn query_server_connectable(&self) -> Result<TcpStream, OpaqueError> { async fn query_server_connectable(&self) -> Result<TcpStream, OpaqueError> {
let address = self let address = self.get_internal_address();
.get_internal_address()
.ok_or_else(|| "failed to get internal address from server")?;
let server_stream = TcpStream::connect(address) let server_stream = TcpStream::connect(address)
.await .await
.map_err(|_| "failed to connect to minecraft server")?; .map_err(|_| "failed to connect to minecraft server")?;