Compare commits

18 Commits

Author SHA1 Message Date
acoolname 54b6631fd0 feat(example): add factorio image example
Build and Push Docker Image / Build image (push) Successful in 1m50s
2026-06-28 22:06:12 +03:00
acoolname 04ad3616c9 chore(filebrowser): migrate from filebrowser to filebrowser quantum
Build and Push Docker Image / Build image (push) Successful in 9m10s
2026-06-28 19:22:35 +03:00
acoolname 7158764d2c fix(browser): change auth method to noauth
Build and Push Docker Image / Build image (push) Successful in 1m45s
2026-06-22 20:12:02 +00:00
acoolname 1f611efed0 fix(browser): set group mount policy
Build and Push Docker Image / Build image (push) Successful in 1m46s
2026-06-22 20:01:03 +00:00
acoolname 935861a107 fix(file-browser): change auth mode to proxy
Build and Push Docker Image / Build image (push) Successful in 1m48s
2026-06-22 19:42:52 +00:00
acoolname 926828a2dc fix(filebrowser): add admin perm to users
Build and Push Docker Image / Build image (push) Successful in 1m49s
2026-01-10 22:14:51 +02:00
acoolname 9ec3ed0f1c fix(filebrowser): fix filebrowser image setter
Build and Push Docker Image / Build image (push) Successful in 6m4s
2025-09-23 13:23:42 +03:00
acoolname e075ff13fa fixed bug where controller did not look at protocol
Build and Push Docker Image / Build image (push) Successful in 1m54s
2025-05-26 16:47:26 +03:00
acoolname 09e9b5dabd added factorio image
Build and Push Docker Image / Build image (push) Successful in 2m0s
2025-05-22 23:07:50 +03:00
acoolname 95a1042c41 fix bug 2025-05-22 23:07:24 +03:00
acoolname 061f8d6d07 added volume as a config in images
Build and Push Docker Image / Build image (push) Successful in 2m6s
2025-05-22 22:37:30 +03:00
acoolname 3a85e44ad4 updated readme 2025-05-19 15:35:27 +03:00
acoolname 4ebb402015 fixed stupid bug
Build and Push Docker Image / Build image (push) Successful in 1m45s
2025-04-09 17:15:02 +03:00
acoolname a54c905cc7 updated deployment to latest version
Build and Push Docker Image / Build image (push) Has been cancelled
2025-04-07 13:48:24 +03:00
acoolname 8f005effa3 fixed bug where server ports is nil
Build and Push Docker Image / Build image (push) Has been cancelled
2025-04-07 13:45:53 +03:00
acoolname 7776d546e9 fixed bug where server ports is nil
Build and Push Docker Image / Build image (push) Successful in 1m45s
2025-04-07 13:36:07 +03:00
acoolname 7ef824830e added command and args to status
Build and Push Docker Image / Build image (push) Has been cancelled
2025-04-05 22:35:43 +03:00
acoolname 39f1c0d92c added config path to file
Build and Push Docker Image / Build image (push) Has been cancelled
2025-04-05 17:15:00 +03:00
21 changed files with 629 additions and 242 deletions
+203 -64
View File
@@ -1,100 +1,240 @@
# kubernetes-operator
// TODO(user): Add simple overview of use/purpose
# ServerManager Kubernetes Operator
A kubernetes operator used to manage servers using CRDs
## Description
// TODO(user): An in-depth paragraph about your project and overview of use
## Getting Started
This is a kubernetes operator used to manage game servers and is used in the implementation of the kubernetes instance manager in the [ServerManager backend](https://git.acooldomain.co/server-manager/backend)
### Prerequisites
- go version v1.22.0+
- docker version 17.03+.
- kubectl version v1.11.3+.
- Access to a Kubernetes v1.11.3+ cluster.
## Important
### To Deploy on the cluster
**Build and push your image to the location specified by `IMG`:**
Currently the only supported ingress is traefik using the `IngressRoute` CRD
## Adding ServerManager operator to your cluster
### Install the CRDs
To install the CRDs you must first clone this repository using:
```sh
make docker-build docker-push IMG=<some-registry>/kubernetes-operator:tag
git clone https://git.acooldomain.co/server-manager/kubernetes-operator servermanager-operator
cd servermanager-operator
```
**NOTE:** This image ought to be published in the personal registry you specified.
And it is required to have access to pull the image from the working environment.
Make sure you have the proper permission to the registry if the above commands dont work.
**Install the CRDs into the cluster:**
After the repository is cloned to install the CRDs just apply them using:
```sh
make install
kubectl -f config/crd
```
**Deploy the Manager to the cluster with the image specified by `IMG`:**
### Setting up the ServerManager Operator
#### Configuration
The server-manager operator takes a configuration file called config.yaml and expects to find it in the same namespace under the name `server-manager-config` with a key named `config.yaml` containing a yaml file in the following schema
```yaml
domain_label: string
default_domain: string
browser:
domain: string
sub_path: string
auth_header: string
cert_resolver: string
entrypoints:
- string
additional_routes:
- IngressRouteRoute
middleware:
name: string
namespace: string
```
#### Configuration values
> **domain_label**
> optional
> type: string
> description: The name of a label on the node that it's value is a DNS record that points to that node.
> **default_domain**
> type: string
> description: The domain to use if the label in `domain_label` is not present on the node the pod runs on
> **browser.domain**
> type: string
> description: The domain that all file browsers would run on
> **browser.sub_path**
> type: string
> description: A path prefix that all browsers will run under
> **browser.auth_header**
> type: string
> description: The header used to identify a user on the browser container
> **browser.additional_routes**
> type: IngressRouteRoute
> description: Any additional routes that would point to anything other than the browser, usually needed for some forwardAuth middlewares
> **browser.middleware**
> type: MiddlewareRef
> description: A reference to the middleware that will handle authentication for the browsers
#### Example configuration
An example configuration that is close to what is used in [ServerManager](https://games.acooldomain.co)
```yaml
domain_label: "ddns.acooldomain.co/hostname"
default_domain: "acooldomain.co"
browser:
domain: games.acooldomain.co
sub_path: /browsers
auth_header: x-authentik-username
cert_resolver: letsencrypt
entrypoints:
- websecure
additional_routes: # This additional route is required for the Authentik middleware
- kind: Rule
match: "Host(`games.acooldomain.co`) && PathPrefix(`/outpost.goauthentik.io/`)"
priority: 15
services:
- kind: Service
name: ak-outpost-traefik
namespace: authentik
port: 9000
middleware:
name: authentik
namespace: authentik
```
And in a `ConfigMap` it looks as follows (replace `<namespace>` with the desired namespace):
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: server-manager-config
namespace: <namespace>
data:
config.yaml: |
domain_label: "ddns.acooldomain.co/hostname"
default_domain: "acooldomain.co"
browser:
domain: games.acooldomain.co
sub_path: /browsers
auth_header: x-authentik-username
cert_resolver: letsencrypt
entrypoints:
- websecure
additional_routes:
- kind: Rule
match: "Host(`games.acooldomain.co`) && PathPrefix(`/outpost.goauthentik.io/`)"
priority: 15
services:
- kind: Service
name: ak-outpost-traefik
namespace: authentik
port: 9000
middleware:
name: authentik
namespace: authentik
```
### Roles
The servermanager-operator requires permissions to **modify/create/delete** `pods`, `pvcs`, `services`, and `ingressroutes.traefik.io` to allow full functionality
to create a service account with these permissions run the following commands where `<namespace>` is the namespace the operator would be deployed to
```sh
make deploy IMG=<some-registry>/kubernetes-operator:tag
kubectl apply -n <namespace> -f https://git.acooldomain.co/server-manager/kubernetes-operator/raw/branch/main/config/rbac/service-account.yaml
kubectl apply -n <namespace> -f https://git.acooldomain.co/server-manager/kubernetes-operator/raw/branch/main/config/rbac/role.yaml
kubectl apply -n <namespace> -f https://git.acooldomain.co/server-manager/kubernetes-operator/raw/branch/main/config/rbac/role-binding.yaml
```
> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
privileges or be logged in as admin.
### Manager
**Create instances of your solution**
You can apply the samples (examples) from the config/sample:
To install the operator itself after all the setup is complete simply run:
```sh
kubectl apply -k config/samples/
kubectl apply -n <namespace> -f https://git.acooldomain.co/server-manager/kubernetes-operator/raw/branch/main/config/manager/manager.yaml
```
>**NOTE**: Ensure that the samples has default values to test it out.
### Verify
### To Uninstall
**Delete the instances (CRs) from the cluster:**
After all the setup is applied you can test that the kubernetes-operator is working by creating a game image and a server that uses it.
Below are exxamples for an Image that runs a minecraft paper server on version 1.21.5 and a Server using that image.
```sh
kubectl delete -k config/samples/
#### Example Image
```yaml
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: Image
metadata:
name: minecraft-paper-1-21-5
spec:
location: git.acooldomain.co/server-manager/minecraft:paper-1.21.5
name: minecraft
tag: paper-1.21.5
working_dir: /opt/server
ports:
- port: 25565
protocol: TCP
init_script:
image: alpine:latest
command:
- /bin/sh
args:
- /bin/sh
- "-c"
- "echo eula=true >> /data/eula.txt"
```
**Delete the APIs(CRDs) from the cluster:**
#### Example Server
```sh
make uninstall
```yaml
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: ServerManager
metadata:
name: test-server
spec:
storage: 10Gi
browser:
"on": true
server:
"on": true
image: minecraft-paper-1-21-5
ports:
```
**UnDeploy the controller from the cluster:**
#### Examples result
```sh
make undeploy
```
After applying the following resources your environment should have several new resources created
## Project Distribution
* PVCs
* test-server
* test-server-browser
Following are the steps to build the installer and distribute this project to users.
* services
* test-server
* type: NodePort
* ContainerPort: 25565
* NodePort: Random Port
1. Build the installer for the image built and published in the registry:
* Pods
* test-server
* test-server-browser
```sh
make build-installer IMG=<some-registry>/kubernetes-operator:tag
```
NOTE: The makefile target mentioned above generates an 'install.yaml'
file in the dist directory. This file contains all the resources built
with Kustomize, which are necessary to install this project without
its dependencies.
2. Using the installer
Users can just run kubectl apply -f <URL for YAML BUNDLE> to install the project, i.e.:
```sh
kubectl apply -f https://raw.githubusercontent.com/<org>/kubernetes-operator/<tag or branch>/dist/install.yaml
```
## Contributing
// TODO(user): Add detailed information on how you would like others to contribute to this project
**NOTE:** Run `make help` for more information on all potential `make` targets
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
To delete the server and the related resources it is enough to delete the `server-manager.acooldomain.co` resource we created
## License
@@ -111,4 +251,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+1
View File
@@ -37,6 +37,7 @@ type ImageSpec struct {
Tag string `json:"tag"`
Location string `json:"location"`
WorkingDir string `json:"working_dir"`
Volume string `json:"volume,omitempty"`
Command []string `json:"command,omitempty"`
Ports []Port `json:"ports"`
Args []string `json:"args,omitempty"`
+2
View File
@@ -44,6 +44,8 @@ type ServerStatus struct {
Domain string `json:"domain,omitempty"`
Running bool `json:"running,omitempty"`
HostPorts []PortMapping `json:"host_ports,omitempty"`
Args []string `json:"args,omitempty"`
Command []string `json:"command,omitempty"`
}
type ServerSpec struct {
+10
View File
@@ -333,6 +333,16 @@ func (in *ServerStatus) DeepCopyInto(out *ServerStatus) {
*out = make([]PortMapping, len(*in))
copy(*out, *in)
}
if in.Args != nil {
in, out := &in.Args, &out.Args
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Command != nil {
in, out := &in.Command, &out.Command
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerStatus.
+6 -1
View File
@@ -146,8 +146,13 @@ func main() {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
configPath := os.Getenv("CONFIG_PATH")
if configPath == "" {
configPath = "config.yaml"
}
config := &controller.ServerManagerReconcilerConfig{}
configData, err := os.ReadFile("config.yaml")
configData, err := os.ReadFile(configPath)
if err != nil {
setupLog.Error(err, "unable to read config file")
}
-23
View File
@@ -1,23 +0,0 @@
domain_label: "ddns.acooldomain.co/hostname"
default_domain: "acooldomain.co"
browser:
domain: games.acooldomain.co
sub_path: /browsers
auth_header: x-authentik-username
cert_resolver: letsencrypt
entrypoints:
- websecure
additional_routes:
- kind: Rule
match: "Host(`games.acooldomain.co`) && PathPrefix(`/outpost.goauthentik.io/`)"
priority: 15
services:
- kind: Service
name: ak-outpost-traefik
namespace: authentik
port: 9000
middleware:
name: authentik
namespace: authentik
@@ -85,6 +85,8 @@ spec:
type: array
tag:
type: string
volume:
type: string
working_dir:
type: string
required:
@@ -94,6 +94,14 @@ spec:
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
properties:
args:
items:
type: string
type: array
command:
items:
type: string
type: array
domain:
type: string
host_ports:
+31
View File
@@ -0,0 +1,31 @@
# https://kubernetes.io/docs/concepts/configuration/configmap/
apiVersion: v1
kind: ConfigMap
metadata:
name: server-manager-config
namespace: server-manager
data:
config.yaml: |
domain_label: "ddns.acooldomain.co/hostname"
default_domain: "acooldomain.co"
browser:
domain: games.acooldomain.co
sub_path: /browsers
auth_header: x-authentik-username
cert_resolver: letsencrypt
entrypoints:
- websecure
additional_routes:
- kind: Rule
match: "Host(`games.acooldomain.co`) && PathPrefix(`/outpost.goauthentik.io/`)"
priority: 15
services:
- kind: Service
name: ak-outpost-traefik
namespace: authentik
port: 9000
middleware:
name: authentik
namespace: authentik
+1
View File
@@ -1,2 +1,3 @@
resources:
- config.yaml
- manager.yaml
+19 -21
View File
@@ -1,32 +1,22 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: controller-manager
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
name: servermanager-manager
labels:
control-plane: controller-manager
control-plane: servermanager-manager
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
spec:
selector:
matchLabels:
control-plane: controller-manager
control-plane: servermanager-manager
replicas: 1
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: manager
labels:
control-plane: controller-manager
control-plane: servermanager-manager
spec:
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
# according to the platforms which are supported by your solution.
@@ -48,6 +38,11 @@ spec:
# operator: In
# values:
# - linux
volumes:
- name: config
configMap:
name: server-manager-config
securityContext:
runAsNonRoot: true
# TODO(user): For common cases that do not require escalating privileges
@@ -55,15 +50,18 @@ spec:
# More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
# Please uncomment the following code if your project does NOT have to work on old Kubernetes
# versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
# seccompProfile:
# type: RuntimeDefault
seccompProfile:
type: RuntimeDefault
containers:
- command:
- /manager
args:
- --leader-elect
- --health-probe-bind-address=:8081
image: controller:latest
image: git.acooldomain.co/server-manager/kubernetes-operator:v0.0.9
env:
- name: CONFIG_PATH
value: /etc/server-manager/config.yaml
volumeMounts:
- name: config
mountPath: /etc/server-manager
name: manager
securityContext:
allowPrivilegeEscalation: false
@@ -91,5 +89,5 @@ spec:
requests:
cpu: 10m
memory: 64Mi
serviceAccountName: controller-manager
serviceAccountName: server-manager
terminationGracePeriodSeconds: 10
+4 -4
View File
@@ -4,12 +4,12 @@ metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: manager-rolebinding
name: server-manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: manager-role
name: server-manager-role
subjects:
- kind: ServiceAccount
name: controller-manager
namespace: system
name: server-manager
namespace: server-manager
+2 -2
View File
@@ -4,5 +4,5 @@ metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: controller-manager
namespace: system
name: server-manager
namespace: server-manager
-62
View File
@@ -1,62 +0,0 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: manager-role
rules:
- resources:
- persistentvolumeclaims
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- resources:
- pods
verbs:
- create
- get
- list
- watch
- apiGroups:
- server-manager.acooldomain.co
resources:
- servermanagers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- server-manager.acooldomain.co
resources:
- servermanagers/finalizers
verbs:
- update
- apiGroups:
- server-manager.acooldomain.co
resources:
- servermanagers/status
verbs:
- get
- patch
- update
- apiGroups:
- traefik.io
resources:
- ingressroutes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
+32
View File
@@ -0,0 +1,32 @@
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: Image
metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: factorio-2-0-47
spec:
location: git.acooldomain.co/server-manager/factorio:2.0.47
name: factorio
tag: 2.0.47
working_dir: /opt/factorio
volume: /opt/server
command:
- /opt/factorio/bin/x64/factorio
- --port
- "34197"
args:
- --start-server
- saves/default.zip
ports:
- port: 34197
protocol: TCP
init_script:
image: alpine:latest
command:
- /bin/sh
args:
- /bin/sh
- "-c"
- "cp /opt/factorio /opt/server"
+24
View File
@@ -0,0 +1,24 @@
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: Image
metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: minecraft-paper-1-21-5
spec:
location: git.acooldomain.co/server-manager/minecraft:paper-1.21.5
name: minecraft
tag: paper-1.21.5
working_dir: /opt/server
ports:
- port: 25565
protocol: TCP
init_script:
image: alpine:latest
command:
- /bin/sh
args:
- /bin/sh
- "-c"
- "echo eula=true >> /data/eula.txt"
@@ -0,0 +1,24 @@
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: Image
metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: minecraft-fabric-1-21-5
spec:
location: git.acooldomain.co/server-manager/minecraft:fabric-1.21.5
name: minecraft
tag: fabric-1.21.5
working_dir: /opt/server
ports:
- port: 25565
protocol: TCP
init_script:
image: alpine:latest
command:
- /bin/sh
args:
- /bin/sh
- "-c"
- "echo eula=true >> /data/eula.txt"
@@ -0,0 +1,24 @@
apiVersion: server-manager.acooldomain.co/v1alpha1
kind: Image
metadata:
labels:
app.kubernetes.io/name: kubernetes-operator
app.kubernetes.io/managed-by: kustomize
name: minecraft-paper-1-21-5
spec:
location: git.acooldomain.co/server-manager/minecraft:paper-1.21.5
name: minecraft
tag: paper-1.21.5
working_dir: /opt/server
ports:
- port: 25565
protocol: TCP
init_script:
image: alpine:latest
command:
- /bin/sh
args:
- /bin/sh
- "-c"
- "echo eula=true >> /data/eula.txt"
@@ -7,7 +7,7 @@ metadata:
name: minecraft-paper-1-21-4
spec:
location: git.acooldomain.co/server-manager/minecraft:paper-1.21.4
name: minecraft:paper-1.21.4
name: minecraft
tag: paper-1.21.4
working_dir: /opt/server
ports:
+66
View File
@@ -0,0 +1,66 @@
package controller
type SourceRule struct {
FolderPath string `yaml:"folderPath"`
Viewable bool `yaml:"viewable"`
}
type SourceConfig struct {
DefaultEnabled bool `yaml:"defaultEnabled"`
Rules []SourceRule `yaml:"rules"`
}
type SourceDefinition struct {
Path string `yaml:"path"`
Config SourceConfig `yaml:"config"`
}
type ServerConfig struct {
Port int `yaml:"port"`
BaseUrl string `yaml:"baseURL"`
Sources []SourceDefinition `yaml:"sources"`
}
type UserDefaultPermissionConfig struct {
Modify bool `yaml:"modify"`
Share bool `yaml:"share"`
Realtime bool `yaml:"realtime"`
Create bool `yaml:"create"`
Delete bool `yaml:"delete"`
Api bool `yaml:"api"`
}
type UserDefaultsConfig struct {
Permissions UserDefaultPermissionConfig `yaml:"permissions"`
}
type AuthPasswordMethodConfig struct {
Enabled bool `yaml:"enabled"`
MinLength int `yaml:"minLength"`
}
type AuthProxyMethodConfig struct {
Enabled bool `yaml:"enabled"`
Header string `yaml:"header"`
}
type AuthMethodsConfig struct {
Password AuthPasswordMethodConfig `yaml:"password"`
Proxy AuthProxyMethodConfig `yaml:"proxy"`
}
type AuthConfig struct {
AdminUsername string `yaml:"adminUsername"`
Methods AuthMethodsConfig `yaml:"methods"`
}
type FrontendConfig struct {
Name string `yaml:"name"`
}
type FileBrowserConfig struct {
Server ServerConfig `yaml:"server"`
Auth AuthConfig `yaml:"auth"`
Frontend FrontendConfig `yaml:"frontend"`
UserDefaults UserDefaultsConfig `yaml:"userDefaults"`
}
+136 -31
View File
@@ -23,6 +23,7 @@ import (
"time"
traefikv3 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
@@ -157,6 +158,11 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
logging.Error(err, "Failed to get image")
return reconcile.Result{}, err
}
if len(s.Spec.Server.Ports) == 0 {
s.Spec.Server.Ports = image.Spec.Ports
err := r.Update(ctx, s)
return reconcile.Result{}, err
}
serverPod := r.ServerPod(s, pvc, image)
found := &corev1.Pod{}
@@ -185,11 +191,27 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
s.Status.Server.Running = true
statusChanged = true
}
if s.Status.Server.Command == nil {
s.Status.Server.Command = serverPod.Spec.Containers[0].Command
statusChanged = true
}
if s.Status.Server.Args == nil {
s.Status.Server.Args = serverPod.Spec.Containers[0].Args
statusChanged = true
}
default:
if s.Status.Server.Running {
s.Status.Server.Running = false
statusChanged = true
}
if len(s.Status.Server.Args) != 0 {
s.Status.Server.Args = nil
statusChanged = true
}
if len(s.Status.Server.Command) != 0 {
s.Status.Server.Command = nil
statusChanged = true
}
}
}
@@ -198,6 +220,14 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
s.Status.Server.Running = false
statusChanged = true
}
if len(s.Status.Server.Args) != 0 {
s.Status.Server.Args = nil
statusChanged = true
}
if len(s.Status.Server.Command) != 0 {
s.Status.Server.Command = nil
statusChanged = true
}
}
logging.Info("verified pod")
@@ -268,7 +298,22 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
logging.Info("verified server service")
browserPod := r.BrowserPod(s, pvc, browserPvc)
browserConfigMap, err := r.BrowserConfigMap(s)
if err != nil {
return ctrl.Result{}, err
}
logging.Info("created browser configmap")
var foundBrowserConfigMap corev1.ConfigMap
err = r.Get(ctx, client.ObjectKeyFromObject(browserConfigMap), &foundBrowserConfigMap)
if err != nil {
logging.Info("Failed getting browser configmap", "err", err)
err = r.Create(ctx, browserConfigMap)
return ctrl.Result{Requeue: true}, err
}
logging.Info("verified browser config map")
browserPod := r.BrowserPod(s, pvc, browserPvc, browserConfigMap)
foundBrowser := &corev1.Pod{}
err = r.Get(ctx, client.ObjectKeyFromObject(browserPod), foundBrowser)
if err == nil && !s.Spec.Browser.On {
@@ -301,7 +346,7 @@ func (r *ServerManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
}
}
if errors.IsNotFound(err) && !s.Spec.Server.On {
if errors.IsNotFound(err) && !s.Spec.Browser.On {
if s.Status.Browser.Running {
s.Status.Browser.Running = false
statusChanged = true
@@ -368,7 +413,7 @@ func (r *ServerManagerReconciler) GenerateBrowserUrl(s *servermanagerv1alpha1.Se
}
func (r *ServerManagerReconciler) GenerateBrowserSubPath(s *servermanagerv1alpha1.ServerManager) string {
if r.Config.Browser.SubPath == "" {
if r.Config.Browser.SubPath != "" {
return fmt.Sprintf("%s/%s/%s", r.Config.Browser.SubPath, s.Namespace, s.Name)
} else {
return fmt.Sprintf("/%s/%s", s.Namespace, s.Name)
@@ -490,16 +535,72 @@ func (r *ServerManagerReconciler) BrowserService(s *servermanagerv1alpha1.Server
return service
}
func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim, browserPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
ports := make([]corev1.ContainerPort, len(s.Spec.Server.Ports))
func (r *ServerManagerReconciler) BrowserConfigMap(s *servermanagerv1alpha1.ServerManager) (*corev1.ConfigMap, error) {
serializedConfig, err := yaml.Marshal(FileBrowserConfig{
Server: ServerConfig{
Port: 80,
BaseUrl: r.GenerateBrowserSubPath(s),
Sources: []SourceDefinition{
{
Path: "/tmp/data",
Config: SourceConfig{
DefaultEnabled: true,
Rules: []SourceRule{
{
FolderPath: "lost+found",
Viewable: false,
},
},
},
},
},
},
Auth: AuthConfig{
AdminUsername: "admin",
Methods: AuthMethodsConfig{
Password: AuthPasswordMethodConfig{
Enabled: false,
},
Proxy: AuthProxyMethodConfig{
Enabled: true,
Header: r.Config.Browser.AuthHeader,
},
},
},
Frontend: FrontendConfig{
Name: "ACoolFileBrowser",
},
UserDefaults: UserDefaultsConfig{
Permissions: UserDefaultPermissionConfig{
Modify: true,
Create: true,
Delete: true,
Api: true,
Share: true,
Realtime: true,
},
},
})
for i, port := range s.Spec.Server.Ports {
ports[i] = corev1.ContainerPort{
ContainerPort: port.Port,
Protocol: port.Protocol,
if err != nil {
return nil, err
}
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-browser-config", s.Name),
Namespace: s.Namespace,
Labels: map[string]string{"browser": s.Name},
},
Data: map[string]string{"config.yaml": string(serializedConfig)},
}, nil
}
func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim, browserPvc *corev1.PersistentVolumeClaim, browserConfigMap *corev1.ConfigMap) *corev1.Pod {
var fsGroupValue int64 = 2000
var runAsUserValue int64 = 1000
policy := corev1.FSGroupChangeOnRootMismatch // Optimizes startup speed
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-browser", s.Name),
@@ -507,6 +608,12 @@ func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerMana
Labels: map[string]string{"browser": s.Name},
},
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{
FSGroup: &fsGroupValue,
RunAsUser: &runAsUserValue,
RunAsGroup: &fsGroupValue,
FSGroupChangePolicy: &policy,
},
Volumes: []corev1.Volume{
{
Name: "volume",
@@ -524,19 +631,12 @@ func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerMana
},
},
},
{Name: "config-volume",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: browserConfigMap.Name,
},
InitContainers: []corev1.Container{
{
Name: "proxy-setter",
Image: "filebrowser/filebrowser",
ImagePullPolicy: corev1.PullIfNotPresent,
Ports: ports,
Command: []string{"/bin/sh"},
Args: []string{"-c", fmt.Sprintf("rm /tmp/database/filebrowser.db; /filebrowser config init -d /tmp/database/filebrowser.db && /filebrowser config set --auth.method=proxy --auth.header=%s -d /tmp/database/filebrowser.db", r.Config.Browser.AuthHeader)},
VolumeMounts: []corev1.VolumeMount{
{
Name: "browser-volume",
MountPath: "/tmp/database",
},
},
},
@@ -544,10 +644,9 @@ func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerMana
Containers: []corev1.Container{
{
Name: "browser",
Image: "filebrowser/filebrowser",
Image: "ghcr.io/gtsteffaniak/filebrowser",
ImagePullPolicy: corev1.PullIfNotPresent,
Ports: ports,
Args: []string{"-d", "/tmp/database/filebrowser.db", "-r", "/tmp/data", "-b", r.GenerateBrowserSubPath(s)},
Args: []string{"-c", "/tmp/config/config.yaml"},
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume",
@@ -557,6 +656,10 @@ func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerMana
Name: "browser-volume",
MountPath: "/tmp/database",
},
{
Name: "config-volume",
MountPath: "/tmp/config",
},
},
},
},
@@ -570,7 +673,7 @@ func (r *ServerManagerReconciler) BrowserPod(s *servermanagerv1alpha1.ServerMana
func (r *ServerManagerReconciler) ServerService(s *servermanagerv1alpha1.ServerManager) *corev1.Service {
ports := make([]corev1.ServicePort, len(s.Spec.Server.Ports))
for i, port := range s.Spec.Server.Ports {
ports[i] = corev1.ServicePort{NodePort: 0, Port: port.Port, TargetPort: intstr.FromInt32(port.Port), Name: fmt.Sprintf("%s-%d", strings.ToLower(string(port.Protocol)), port.Port)}
ports[i] = corev1.ServicePort{NodePort: 0, Port: port.Port, TargetPort: intstr.FromInt32(port.Port), Name: fmt.Sprintf("%s-%d", strings.ToLower(string(port.Protocol)), port.Port), Protocol: port.Protocol}
}
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
@@ -611,13 +714,10 @@ func (r *ServerManagerReconciler) ServerPvc(s *servermanagerv1alpha1.ServerManag
func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManager, pvc *corev1.PersistentVolumeClaim, image *servermanagerv1alpha1.Image) *corev1.Pod {
serverPorts := image.Spec.Ports
if len(s.Spec.Server.Ports) > 0 {
serverPorts = s.Spec.Server.Ports
}
ports := make([]corev1.ContainerPort, len(serverPorts))
for i, port := range s.Spec.Server.Ports {
for i, port := range serverPorts {
ports[i] = corev1.ContainerPort{
ContainerPort: port.Port,
Protocol: port.Protocol,
@@ -642,7 +742,7 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag
Name: "init",
Image: image.Spec.InitScript.Image,
ImagePullPolicy: corev1.PullIfNotPresent,
Command: image.Spec.Command,
Command: image.Spec.InitScript.Command,
Args: image.Spec.InitScript.Args,
VolumeMounts: []corev1.VolumeMount{
{
@@ -654,6 +754,11 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag
}
}
volumePath := image.Spec.Volume
if volumePath == "" {
volumePath = image.Spec.WorkingDir
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
@@ -683,7 +788,7 @@ func (r *ServerManagerReconciler) ServerPod(s *servermanagerv1alpha1.ServerManag
Ports: ports,
VolumeMounts: []corev1.VolumeMount{{
Name: "volume",
MountPath: image.Spec.WorkingDir,
MountPath: volumePath,
}},
Stdin: true,
TTY: true,