From 2ac8f8f6d32caea2535a472eb8d113c5c089a3ba Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Tue, 15 Mar 2022 23:11:05 +0200 Subject: [PATCH] feat(koel): Add koel (#2182) * feat(koel): Add koel * fix secrets * update image * lint * fix secet ref * rofs * root --- charts/incubator/koel/Chart.yaml | 33 ++ charts/incubator/koel/questions.yaml | 433 +++++++++++++++++++ charts/incubator/koel/templates/_secrets.tpl | 20 + charts/incubator/koel/templates/common.yaml | 8 + charts/incubator/koel/values.yaml | 59 +++ docs/_static/img/appicons/koel.png | Bin 0 -> 18016 bytes docs/apps/stable/notes.md | 4 + docs/manual/default-ports.md | 1 + 8 files changed, 558 insertions(+) create mode 100644 charts/incubator/koel/Chart.yaml create mode 100644 charts/incubator/koel/questions.yaml create mode 100644 charts/incubator/koel/templates/_secrets.tpl create mode 100644 charts/incubator/koel/templates/common.yaml create mode 100644 charts/incubator/koel/values.yaml create mode 100644 docs/_static/img/appicons/koel.png create mode 100644 docs/apps/stable/notes.md diff --git a/charts/incubator/koel/Chart.yaml b/charts/incubator/koel/Chart.yaml new file mode 100644 index 00000000000..5ab949c52c0 --- /dev/null +++ b/charts/incubator/koel/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +appVersion: "10.6.2" +dependencies: +- name: common + repository: https://truecharts.org + version: 9.1.2 +- condition: postgresql.enabled + name: postgresql + repository: https://truecharts.org/ + version: 7.0.2 +description: Koel is a simple web-based personal audio streaming service written in Vue on the client side and Laravel on the server side. +home: https://github.com/truecharts/apps/tree/master/charts/stable/koel +icon: https://truecharts.org/_static/img/appicons/koel.png +keywords: +- koel +- music +- stream +kubeVersion: '>=1.16.0-0' +maintainers: +- email: info@truecharts.org + name: TrueCharts + url: https://truecharts.org +name: koel +sources: +- https://github.com/koel/docker +- https://hub.docker.com/r/hyzual/koel +- https://github.com/koel/koel +version: 0.0.1 +annotations: + truecharts.org/catagories: | + - media + truecharts.org/SCALE-support: "true" + truecharts.org/grade: U diff --git a/charts/incubator/koel/questions.yaml b/charts/incubator/koel/questions.yaml new file mode 100644 index 00000000000..a0935c0cd4b --- /dev/null +++ b/charts/incubator/koel/questions.yaml @@ -0,0 +1,433 @@ +# Include{groups} +portals: + open: + protocols: + - "$kubernetes-resource_configmap_portal_protocol" + host: + - "$kubernetes-resource_configmap_portal_host" + ports: + - "$kubernetes-resource_configmap_portal_port" +questions: + - variable: portal + group: "Container Image" + label: "Configure Portal Button" + schema: + type: dict + hidden: true + attrs: + - variable: enabled + label: "Enable" + description: "enable the portal button" + schema: + hidden: true + editable: false + type: boolean + default: true +# Include{global} + - variable: controller + group: "Controller" + label: "" + schema: + additional_attrs: true + type: dict + attrs: + - variable: advanced + label: "Show Advanced Controller Settings" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: type + description: "Please specify type of workload to deploy" + label: "(Advanced) Controller Type" + schema: + type: string + default: "deployment" + required: true + enum: + - value: "deployment" + description: "Deployment" + - value: "statefulset" + description: "Statefulset" + - value: "daemonset" + description: "Daemonset" + - variable: replicas + description: "Number of desired pod replicas" + label: "Desired Replicas" + schema: + type: int + default: 1 + required: true + - variable: strategy + description: "Please specify type of workload to deploy" + label: "(Advanced) Update Strategy" + schema: + type: string + default: "Recreate" + required: true + enum: + - value: "Recreate" + description: "Recreate: Kill existing pods before creating new ones" + - value: "RollingUpdate" + description: "RollingUpdate: Create new pods and then kill old ones" + - value: "OnDelete" + description: "(Legacy) OnDelete: ignore .spec.template changes" +# Include{controllerExpert} + - variable: secret + group: "Container Configuration" + label: "Image Secrets" + schema: + additional_attrs: true + type: dict + attrs: + - variable: LASTFM_API_KEY + label: "LASTFM_API_KEY" + description: "Enable scrobbling to Last.fm. See https://docs.koel.dev/3rd-party.html#last-fm" + schema: + type: string + default: "" + - variable: LASTFM_API_KEY + label: "LASTFM_API_SECRET" + description: "Enable scrobbling to Last.fm. See https://docs.koel.dev/3rd-party.html#last-fm" + schema: + type: string + default: "" + + - variable: env + group: "Container Configuration" + label: "Image Environment" + schema: + additional_attrs: true + type: dict + attrs: + - variable: FORCE_HTTPS + label: "FORCE_HTTPS" + description: "If set to true, all URLs redirects done by koel will use https." + schema: + type: boolean + default: false + - variable: MEMORY_LIMIT + label: "MEMORY_LIMIT" + description: "Amount of memory in MB for the scanning process." + schema: + type: int + default: 2048 + +# Include{containerConfig} + + - variable: service + group: "Networking and Services" + label: "Configure Service(s)" + schema: + additional_attrs: true + type: dict + attrs: + - variable: main + label: "Main Service" + description: "The Primary service on which the healthcheck runs, often the webUI" + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelector} + - variable: main + label: "Main Service Port Configuration" + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: "Port" + description: "This port exposes the container port on the service" + schema: + type: int + default: 10185 + required: true + - variable: advanced + label: "Show Advanced settings" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: protocol + label: "Port Type" + schema: + type: string + default: "HTTP" + enum: + - value: HTTP + description: "HTTP" + - value: "HTTPS" + description: "HTTPS" + - value: TCP + description: "TCP" + - value: "UDP" + description: "UDP" + - variable: nodePort + label: "Node Port (Optional)" + description: "This port gets exposed to the node. Only considered when service type is NodePort, Simple or LoadBalancer" + schema: + type: int + min: 9000 + max: 65535 + - variable: targetPort + label: "Target Port" + description: "The internal(!) port on the container the Application runs on" + schema: + type: int + default: 80 + + - variable: serviceexpert + group: "Networking and Services" + label: "Show Expert Config" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: hostNetwork + group: "Networking and Services" + label: "Host-Networking (Complicated)" + schema: + type: boolean + default: false + +# Include{serviceExpert} + +# Include{serviceList} + + - variable: persistence + label: "Integrated Persistent Storage" + description: "Integrated Persistent Storage" + group: "Storage and Persistence" + schema: + additional_attrs: true + type: dict + attrs: + - variable: music + label: "App Music Storage" + description: "Stores the Application Music." + schema: + additional_attrs: true + type: dict + attrs: + - variable: type + label: "Type of Storage" + description: "Sets the persistence type, Anything other than PVC could break rollback!" + schema: + type: string + default: "simplePVC" + enum: + - value: "simplePVC" + description: "PVC (simple)" + - value: "simpleHP" + description: "HostPath (simple)" + - value: "emptyDir" + description: "emptyDir" + - value: "pvc" + description: "pvc" + - value: "hostPath" + description: "hostPath" +# Include{persistenceBasic} + - variable: hostPath + label: "hostPath" + description: "Path inside the container the storage is mounted" + schema: + show_if: [["type", "=", "hostPath"]] + type: hostpath + - variable: medium + label: "EmptyDir Medium" + schema: + show_if: [["type", "=", "emptyDir"]] + type: string + default: "" + enum: + - value: "" + description: "Default" + - value: "Memory" + description: "Memory" +# Include{persistenceAdvanced} + - variable: covers + label: "App Covers Storage" + description: "Stores the Application Covers." + schema: + additional_attrs: true + type: dict + attrs: + - variable: type + label: "Type of Storage" + description: "Sets the persistence type, Anything other than PVC could break rollback!" + schema: + type: string + default: "simplePVC" + enum: + - value: "simplePVC" + description: "PVC (simple)" + - value: "simpleHP" + description: "HostPath (simple)" + - value: "emptyDir" + description: "emptyDir" + - value: "pvc" + description: "pvc" + - value: "hostPath" + description: "hostPath" +# Include{persistenceBasic} + - variable: hostPath + label: "hostPath" + description: "Path inside the container the storage is mounted" + schema: + show_if: [["type", "=", "hostPath"]] + type: hostpath + - variable: medium + label: "EmptyDir Medium" + schema: + show_if: [["type", "=", "emptyDir"]] + type: string + default: "" + enum: + - value: "" + description: "Default" + - value: "Memory" + description: "Memory" +# Include{persistenceAdvanced} + - variable: searchindex + label: "App Search Index Storage" + description: "Stores the Application Search Index." + schema: + additional_attrs: true + type: dict + attrs: + - variable: type + label: "Type of Storage" + description: "Sets the persistence type, Anything other than PVC could break rollback!" + schema: + type: string + default: "simplePVC" + enum: + - value: "simplePVC" + description: "PVC (simple)" + - value: "simpleHP" + description: "HostPath (simple)" + - value: "emptyDir" + description: "emptyDir" + - value: "pvc" + description: "pvc" + - value: "hostPath" + description: "hostPath" +# Include{persistenceBasic} + - variable: hostPath + label: "hostPath" + description: "Path inside the container the storage is mounted" + schema: + show_if: [["type", "=", "hostPath"]] + type: hostpath + - variable: medium + label: "EmptyDir Medium" + schema: + show_if: [["type", "=", "emptyDir"]] + type: string + default: "" + enum: + - value: "" + description: "Default" + - value: "Memory" + description: "Memory" +# Include{persistenceAdvanced} + +# Include{persistenceList} + + - variable: ingress + label: "" + group: "Ingress" + schema: + additional_attrs: true + type: dict + attrs: + - variable: main + label: "Main Ingress" + schema: + additional_attrs: true + type: dict + attrs: +# Include{ingressDefault} + +# Include{ingressTLS} + +# Include{ingressTraefik} + +# Include{ingressExpert} + +# Include{ingressList} + +# Include{security} + + - variable: advancedSecurity + label: "Show Advanced Security Settings" + group: "Security and Permissions" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: securityContext + label: "Security Context" + schema: + additional_attrs: true + type: dict + attrs: + - variable: privileged + label: "Privileged mode" + schema: + type: boolean + default: false + - variable: readOnlyRootFilesystem + label: "ReadOnly Root Filesystem" + schema: + type: boolean + default: false + - variable: allowPrivilegeEscalation + label: "Allow Privilege Escalation" + schema: + type: boolean + default: false + - variable: runAsNonRoot + label: "runAsNonRoot" + schema: + type: boolean + default: false +# Include{securityContextAdvanced} + + - variable: podSecurityContext + group: "Security and Permissions" + label: "Pod Security Context" + schema: + additional_attrs: true + type: dict + attrs: + - variable: runAsUser + label: "runAsUser" + description: "The UserID of the user running the application" + schema: + type: int + default: 0 + - variable: runAsGroup + label: "runAsGroup" + description: The groupID this App of the user running the application" + schema: + type: int + default: 0 + - variable: fsGroup + label: "fsGroup" + description: "The group that should own ALL storage." + schema: + type: int + default: 568 +# Include{podSecurityContextAdvanced} + +# Include{resources} + +# Include{advanced} + +# Include{addons} diff --git a/charts/incubator/koel/templates/_secrets.tpl b/charts/incubator/koel/templates/_secrets.tpl new file mode 100644 index 00000000000..314d01fbd8f --- /dev/null +++ b/charts/incubator/koel/templates/_secrets.tpl @@ -0,0 +1,20 @@ +{{/* Define the secrets */}} +{{- define "koel.secrets" -}} +--- + +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: koel-secrets +{{- $koelprevious := lookup "v1" "Secret" .Release.Namespace "koel-secrets" }} +{{- $app_key := "" }} +data: + {{- if $koelprevious}} + APP_KEY: {{ index $koelprevious.data "APP_KEY" }} + {{- else }} + {{- $app_key := randAlphaNum 32 }} + APP_KEY: {{ $app_key | b64enc }} + {{- end }} + +{{- end -}} diff --git a/charts/incubator/koel/templates/common.yaml b/charts/incubator/koel/templates/common.yaml new file mode 100644 index 00000000000..1c2c86f4f82 --- /dev/null +++ b/charts/incubator/koel/templates/common.yaml @@ -0,0 +1,8 @@ +{{/* Make sure all variables are set properly */}} +{{- include "common.setup" . }} + +{{/* Render secrets for koel */}} +{{- include "koel.secrets" . }} + +{{/* Render the templates */}} +{{ include "common.postSetup" . }} diff --git a/charts/incubator/koel/values.yaml b/charts/incubator/koel/values.yaml new file mode 100644 index 00000000000..0a77514b2c5 --- /dev/null +++ b/charts/incubator/koel/values.yaml @@ -0,0 +1,59 @@ +image: + repository: hyzual/koel + tag: latest@sha256:fa4bafbe3ae869f153bdc3bc87db739f300bef6cc94e398d7efa587a443f86fa + pullPolicy: IfNotPresent + +securityContext: + readOnlyRootFilesystem: false + runAsNonRoot: false + +podSecurityContext: + runAsUser: 0 + runAsGroup: 0 + +env: + DB_CONNECTION: "pgsql" + DB_USERNAME: "{{ .Values.postgresql.postgresqlUsername }}" + DB_DATABASE: "{{ .Values.postgresql.postgresqlDatabase }}" + FORCE_HTTPS: false + MEMORY_LIMIT: 2048 + LASTFM_API_KEY: "" + LASTFM_API_SECRET: "" + +service: + main: + ports: + main: + port: 10185 + targetPort: 80 + +envValueFrom: + DB_HOST: + secretKeyRef: + name: dbcreds + key: plainhost + DB_PASSWORD: + secretKeyRef: + name: dbcreds + key: postgresql-password + APP_KEY: + secretKeyRef: + name: koel-secrets + key: APP_KEY + +persistence: + music: + enabled: true + mountPath: "/music" + covers: + enabled: true + mountPath: "/var/www/html/public/img/covers" + searchindex: + enabled: true + mountPath: "/var/www/html/storage/search-indexes" + +postgresql: + enabled: true + existingSecret: "dbcreds" + postgresqlUsername: koel + postgresqlDatabase: koel diff --git a/docs/_static/img/appicons/koel.png b/docs/_static/img/appicons/koel.png new file mode 100644 index 0000000000000000000000000000000000000000..76c88869ad65280bc816d216ad7f0ce5427702fd GIT binary patch literal 18016 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�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