From 91df195f95012f74fabc8b19bc39d0ef55380456 Mon Sep 17 00:00:00 2001 From: cloudymax Date: Sun, 28 Sep 2025 12:29:13 +0200 Subject: [PATCH 1/9] add a storage-class option for grafana stack --- smol_k8s_lab/config/default_config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smol_k8s_lab/config/default_config.yaml b/smol_k8s_lab/config/default_config.yaml index 48d08e422..003436d85 100644 --- a/smol_k8s_lab/config/default_config.yaml +++ b/smol_k8s_lab/config/default_config.yaml @@ -2469,6 +2469,8 @@ apps: s3_endpoint: "" # capacity for the PVC backing your local s3 instance s3_pvc_capacity: 100Gi + # StorageClass for the PVC backing your local s3 instance + s3_storage_class: local-path # git repo to install the Argo CD app from repo: https://github.com/small-hack/argocd-apps From 7f94a79681bf2efa9ee95dfe128362bc977a0cfd Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 13:04:01 +0200 Subject: [PATCH 2/9] allow ghost source repo: https://small-hack.github.io/ghost-helm-chart --- smol_k8s_lab/config/default_config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/smol_k8s_lab/config/default_config.yaml b/smol_k8s_lab/config/default_config.yaml index 003436d85..0eeb4ca0f 100644 --- a/smol_k8s_lab/config/default_config.yaml +++ b/smol_k8s_lab/config/default_config.yaml @@ -703,6 +703,7 @@ apps: source_repos: - registry-1.docker.io - seaweedfs.github.io/seaweedfs/helm + - https://small-hack.github.io/ghost-helm-chart destination: # automatically includes the app's namespace and argocd's namespace namespaces: [] From 676f075c0b5c85ebd2d47426e72cfee85f6274cd Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 13:04:20 +0200 Subject: [PATCH 3/9] update pyproject version to b3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f36562476..5d2dbe62a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "smol_k8s_lab" -version = "7.0.0b2" +version = "7.0.0b3" description = "CLI and TUI to quickly install slimmer Kubernetes distros and then manage apps declaratively using Argo CD" authors = ["Jesse Hitch ", "Max Roby "] From 25c43851da0172010240405ebdd314b9da2fdbdc Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 13:36:29 +0200 Subject: [PATCH 4/9] chaneg - to _ in grafana backup values --- smol_k8s_lab/k8s_apps/monitoring/grafana_stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smol_k8s_lab/k8s_apps/monitoring/grafana_stack.py b/smol_k8s_lab/k8s_apps/monitoring/grafana_stack.py index bc85b98fa..9af9dc40e 100644 --- a/smol_k8s_lab/k8s_apps/monitoring/grafana_stack.py +++ b/smol_k8s_lab/k8s_apps/monitoring/grafana_stack.py @@ -55,7 +55,7 @@ def configure_grafana_stack(argocd: ArgoCD, if init_enabled: # configure backup s3 credentials - backup_vals = process_backup_vals(cfg.get('backups', ''), 'grafana-stack', argocd) + backup_vals = process_backup_vals(cfg.get('backups', ''), 'grafana_stack', argocd) # initial secrets to deploy this app from scratch if init_enabled and not app_installed: From c1c373f44ec321d0824385fe7892b9ea5d177295 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 13:59:14 +0200 Subject: [PATCH 5/9] update ghost stuff to the latest --- docs/k8s_apps/experimental/ghost.md | 24 +++---- smol_k8s_lab/config/default_config.yaml | 23 +++---- smol_k8s_lab/k8s_apps/social/ghost.py | 87 +++++++------------------ 3 files changed, 39 insertions(+), 95 deletions(-) diff --git a/docs/k8s_apps/experimental/ghost.md b/docs/k8s_apps/experimental/ghost.md index c2d4fb715..b390ab852 100644 --- a/docs/k8s_apps/experimental/ghost.md +++ b/docs/k8s_apps/experimental/ghost.md @@ -22,17 +22,20 @@ apps: init: enabled: false values: + # admin info + admin_user: "" + admin_email: "" + # smtp info + smtp_host: "" + smtp_user: "" + smtp_port: "" + smtp_protocol: "SMTP" smtp_password: value_from: env: GHOST_SMTP_PASSWORD backups: # cronjob syntax schedule to run ghost pvc backups pvc_schedule: 10 0 * * * - # cronjob syntax (with SECONDS field) for ghost postgres backups - # must happen at least 10 minutes before pvc backups, to avoid corruption - # due to missing files. This is because the backup shows as completed before - # it actually is - postgres_schedule: 0 0 0 * * * s3: # these are for pushing remote backups of your local s3 storage, for speed and cost optimization endpoint: "" @@ -60,18 +63,8 @@ apps: # affinity_value: "" # hostname that users go to in the browser hostname: "" - # admin username - admin_user: "ghost" - # admin email - admin_email: "" # title of your title blog_title: "" - # smtp server - smtp_host: "" - # smtp port - smtp_port: "" - # smtp username - smtp_user: "" # ghost mysql pvc capacity mysql_pvc_capacity: 5Gi # ghost pvc capacity @@ -98,6 +91,7 @@ apps: source_repos: - registry-1.docker.io - seaweedfs.github.io/seaweedfs/helm + - https://small-hack.github.io/ghost-helm-chart destination: # automatically includes the app's namespace and argocd's namespace namespaces: [] diff --git a/smol_k8s_lab/config/default_config.yaml b/smol_k8s_lab/config/default_config.yaml index 0eeb4ca0f..ec052f4ac 100644 --- a/smol_k8s_lab/config/default_config.yaml +++ b/smol_k8s_lab/config/default_config.yaml @@ -619,17 +619,20 @@ apps: init: enabled: true values: + # admin info + admin_user: "" + admin_email: "" + # smtp info + smtp_host: "" + smtp_user: "" + smtp_port: "" + smtp_protocol: "SMTP" smtp_password: value_from: env: GHOST_SMTP_PASSWORD backups: # cronjob syntax schedule to run ghost pvc backups pvc_schedule: 10 0 * * * - # cronjob syntax (with SECONDS field) for ghost postgres backups - # must happen at least 10 minutes before pvc backups, to avoid corruption - # due to missing files. This is because the backup shows as completed before - # it actually is - postgres_schedule: 0 0 0 * * * s3: # these are for pushing remote backups of your local s3 storage, for speed and cost optimization endpoint: "" @@ -657,18 +660,8 @@ apps: # affinity_value: "" # hostname that users go to in the browser hostname: "" - # admin username - admin_user: "ghost" - # admin email - admin_email: "" # title of your title blog_title: "" - # smtp server - smtp_host: "" - # smtp port - smtp_port: "" - # smtp username - smtp_user: "" # ghost mysql pvc capacity mysql_pvc_capacity: 5Gi # ghost pvc capacity diff --git a/smol_k8s_lab/k8s_apps/social/ghost.py b/smol_k8s_lab/k8s_apps/social/ghost.py index e884b3e98..6427dbde3 100644 --- a/smol_k8s_lab/k8s_apps/social/ghost.py +++ b/smol_k8s_lab/k8s_apps/social/ghost.py @@ -7,7 +7,6 @@ k8up_restore_pvc) from smol_k8s_lab.utils.passwords import create_password from smol_k8s_lab.utils.rich_cli.console_logging import sub_header, header -from smol_k8s_lab.utils.run.subproc import subproc from smol_k8s_lab.utils.value_from import extract_secret, process_backup_vals # external libraries @@ -82,6 +81,7 @@ async def configure_ghost(argocd: ArgoCD, mail_user = init_values.get('smtp_user', '') mail_host = init_values.get('smtp_host', '') mail_port = init_values.get('smtp_port', '') + mail_protocol = init_values.get('smtp_protocol', '') mail_pass = extract_secret(init_values.get('smtp_password')) # configure s3 credentials @@ -127,7 +127,9 @@ async def configure_ghost(argocd: ArgoCD, backup_vals['s3_password'], backup_vals['restic_repo_pass'], ghost_admin_username, + ghost_admin_email, mail_host, + mail_protocol, mail_port, mail_user, mail_pass, @@ -171,20 +173,6 @@ async def configure_ghost(argocd: ArgoCD, # wait for all the ghost apps to come up, give it extra time argocd.sync_app(app='ghost-web-app', sleep_time=4) argocd.wait_for_app('ghost-web-app') - - # create admin credentials - password = create_user(ghost_admin_username, - ghost_admin_email, - cfg['argo']['namespace']) - if bitwarden: - sub_header("Creating secrets in Bitwarden") - bitwarden.create_login( - name='ghost-admin-credentials', - item_url=ghost_hostname, - user=ghost_admin_username, - password=password, - fields=[create_custom_field("email", ghost_admin_email)] - ) else: log.info("ghost already installed 🎉") @@ -192,45 +180,6 @@ async def configure_ghost(argocd: ArgoCD, refresh_bweso(argocd, ghost_hostname, bitwarden) -def create_user(user: str, email: str, pod_namespace: str) -> str: - """ - given a username, email, and namespace of the ghost pod, we'll create a - new ghost user using a kubectl exec command and then we return - their autogenerated password - """ - sub_header(f"Creating a ghost user for: {user}") - # first, go get the exact name of the pod we need to exec a command on - pod_cmd = ( - f"kubectl get pods -n {pod_namespace} " - "-l app.kubernetes.io/instance=ghost-web-app,app.kubernetes.io/component=web" - " --no-headers " - "-o custom-columns=NAME:.metadata.name" - ) - pod = subproc([pod_cmd]).rstrip() - log.info(f"ghost web app pod is: {pod}") - - # generate a random password - password = create_password() - - # then run the user creation command - cmd = (f'kubectl exec -n {pod_namespace} {pod} -- /bin/sh -c "./ghost ' - '--config-path ../config/config.yaml admin account create ' - f'--username {user} --email {email} --password \'{password}\'"') - - # then process the output from the command - subproc([cmd], shell=True, universal_newlines=True) - - # then run the user promotion (to admin) command - cmd = (f'kubectl exec -n {pod_namespace} {pod} -- /bin/sh -c ' - '"./ghost --config-path ../config/config.yaml admin ' - f'account promote --username {user}"') - - # then process the output from the command - subproc([cmd], shell=True, universal_newlines=True).split()[3] - - return password - - def refresh_bweso(argocd: ArgoCD, ghost_hostname: str, bitwarden: BwCLI) -> None: @@ -290,7 +239,9 @@ def setup_bitwarden_items(argocd: ArgoCD, backups_s3_password: str, restic_repo_pass: str, admin_user: str, + admin_email: str, mail_host: str, + mail_protocol: str, mail_port: str, mail_user: str, mail_pass: str, @@ -347,35 +298,40 @@ def setup_bitwarden_items(argocd: ArgoCD, ghost_mysql_password = bitwarden.generate() mysql_pass_obj = create_custom_field("mysqlPassword", ghost_mysql_password) + mysql_database_obj = create_custom_field("database", "ghost") + mysql_port_obj = create_custom_field("port", "3306") db_id = bitwarden.create_login( name='ghost-mysql-credentials', item_url=ghost_hostname, user='ghost', password=ghost_mysql_password, - fields=[mysql_pass_obj] + fields=[mysql_pass_obj, mysql_database_obj, mysql_port_obj] ) # SMTP credentials ghost_smtp_host_obj = create_custom_field("smtpHostname", mail_host) ghost_smtp_port_obj = create_custom_field("smtpPort", mail_port) + ghost_smtp_protocol_obj = create_custom_field("smtpProtocol", mail_protocol) smtp_id = bitwarden.create_login( name='ghost-smtp-credentials', item_url=ghost_hostname, user=mail_user, password=mail_pass, - fields=[ghost_smtp_host_obj, ghost_smtp_port_obj] + fields=[ghost_smtp_host_obj, + ghost_smtp_port_obj, + ghost_smtp_protocol_obj] ) # admin credentials for ghost itself - # admin_password = create_password() - # email_obj = create_custom_field("email", ghost_admin_email) - # admin_id = bitwarden.create_login( - # name='ghost-admin-credentials', - # item_url=ghost_hostname, - # user=ghost_admin_username, - # password=admin_password, - # fields=[email_obj] - # ) + admin_password = create_password() + email_obj = create_custom_field("email", admin_email) + admin_id = bitwarden.create_login( + name='ghost-admin-credentials', + item_url=ghost_hostname, + user=admin_user, + password=admin_password, + fields=[email_obj] + ) # oidc credentials if they were given, else they're probably already there if oidc_creds: @@ -400,6 +356,7 @@ def setup_bitwarden_items(argocd: ArgoCD, 'ghost_oidc_credentials_bitwarden_id': oidc_id, 'ghost_mysql_credentials_bitwarden_id': db_id, 'ghost_s3_admin_credentials_bitwarden_id': s3_admin_id, + 'ghost_admin_credentials_bitwarden_id': admin_id, 'ghost_s3_ghost_credentials_bitwarden_id': s3_id, 'ghost_s3_backups_credentials_bitwarden_id': s3_backups_id}) From a739e45679ca8ae7c605044283100833dec57a5e Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 14:03:43 +0200 Subject: [PATCH 6/9] add optinal admin hostname for ghost blogs --- docs/k8s_apps/experimental/ghost.md | 2 ++ smol_k8s_lab/config/default_config.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/k8s_apps/experimental/ghost.md b/docs/k8s_apps/experimental/ghost.md index b390ab852..bf5ff7807 100644 --- a/docs/k8s_apps/experimental/ghost.md +++ b/docs/k8s_apps/experimental/ghost.md @@ -63,6 +63,8 @@ apps: # affinity_value: "" # hostname that users go to in the browser hostname: "" + # admin hostname that users go to in the browser + admin_hostname: "" # title of your title blog_title: "" # ghost mysql pvc capacity diff --git a/smol_k8s_lab/config/default_config.yaml b/smol_k8s_lab/config/default_config.yaml index ec052f4ac..2c36b404d 100644 --- a/smol_k8s_lab/config/default_config.yaml +++ b/smol_k8s_lab/config/default_config.yaml @@ -660,6 +660,8 @@ apps: # affinity_value: "" # hostname that users go to in the browser hostname: "" + # admin hostname that users go to in the browser + admin_hostname: "" # title of your title blog_title: "" # ghost mysql pvc capacity From 5b3752558f5c25ac7e0d4ad68f5866788a1ed17d Mon Sep 17 00:00:00 2001 From: Max! Date: Sun, 28 Sep 2025 15:20:47 +0200 Subject: [PATCH 7/9] Fix oauth callback uri for forgejo --- smol_k8s_lab/k8s_apps/social/forgejo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smol_k8s_lab/k8s_apps/social/forgejo.py b/smol_k8s_lab/k8s_apps/social/forgejo.py index 48301f148..d38f03e60 100644 --- a/smol_k8s_lab/k8s_apps/social/forgejo.py +++ b/smol_k8s_lab/k8s_apps/social/forgejo.py @@ -91,7 +91,7 @@ async def configure_forgejo(argocd: ArgoCD, # configure OIDC if zitadel and not restore_enabled: log.debug("Creating a forgejo OIDC application in Zitadel...") - redirect_uris = f"https://{forgejo_hostname}/auth/callback" + redirect_uris = f"https://{forgejo_hostname}/user/oauth2/Zitadel/callback" logout_uris = [f"https://{forgejo_hostname}"] oidc_creds = zitadel.create_application( "forgejo", From 5ed86950bcab6052036bc041928e3b3d20cf9a8a Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 28 Sep 2025 20:24:41 +0200 Subject: [PATCH 8/9] allow setting the smtp from address for ghost --- docs/k8s_apps/experimental/ghost.md | 2 ++ smol_k8s_lab/config/default_config.yaml | 2 ++ smol_k8s_lab/k8s_apps/social/ghost.py | 7 ++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/k8s_apps/experimental/ghost.md b/docs/k8s_apps/experimental/ghost.md index bf5ff7807..6ca2bea66 100644 --- a/docs/k8s_apps/experimental/ghost.md +++ b/docs/k8s_apps/experimental/ghost.md @@ -30,6 +30,8 @@ apps: smtp_user: "" smtp_port: "" smtp_protocol: "SMTP" + # smtp from email (verified sender) + smtp_from_address: example.com smtp_password: value_from: env: GHOST_SMTP_PASSWORD diff --git a/smol_k8s_lab/config/default_config.yaml b/smol_k8s_lab/config/default_config.yaml index 2c36b404d..5c80e2ada 100644 --- a/smol_k8s_lab/config/default_config.yaml +++ b/smol_k8s_lab/config/default_config.yaml @@ -627,6 +627,8 @@ apps: smtp_user: "" smtp_port: "" smtp_protocol: "SMTP" + # smtp from email (verified sender) + smtp_from_address: example.com smtp_password: value_from: env: GHOST_SMTP_PASSWORD diff --git a/smol_k8s_lab/k8s_apps/social/ghost.py b/smol_k8s_lab/k8s_apps/social/ghost.py index 6427dbde3..3bea4d012 100644 --- a/smol_k8s_lab/k8s_apps/social/ghost.py +++ b/smol_k8s_lab/k8s_apps/social/ghost.py @@ -81,6 +81,7 @@ async def configure_ghost(argocd: ArgoCD, mail_user = init_values.get('smtp_user', '') mail_host = init_values.get('smtp_host', '') mail_port = init_values.get('smtp_port', '') + mail_from_address = init_values.get('smtp_from_address', '') mail_protocol = init_values.get('smtp_protocol', '') mail_pass = extract_secret(init_values.get('smtp_password')) @@ -130,6 +131,7 @@ async def configure_ghost(argocd: ArgoCD, ghost_admin_email, mail_host, mail_protocol, + mail_from_address, mail_port, mail_user, mail_pass, @@ -242,6 +244,7 @@ def setup_bitwarden_items(argocd: ArgoCD, admin_email: str, mail_host: str, mail_protocol: str, + mail_from_address: str, mail_port: str, mail_user: str, mail_pass: str, @@ -312,6 +315,7 @@ def setup_bitwarden_items(argocd: ArgoCD, ghost_smtp_host_obj = create_custom_field("smtpHostname", mail_host) ghost_smtp_port_obj = create_custom_field("smtpPort", mail_port) ghost_smtp_protocol_obj = create_custom_field("smtpProtocol", mail_protocol) + ghost_smtp_from_address_obj = create_custom_field("smtpFromAddress", mail_from_address) smtp_id = bitwarden.create_login( name='ghost-smtp-credentials', item_url=ghost_hostname, @@ -319,7 +323,8 @@ def setup_bitwarden_items(argocd: ArgoCD, password=mail_pass, fields=[ghost_smtp_host_obj, ghost_smtp_port_obj, - ghost_smtp_protocol_obj] + ghost_smtp_protocol_obj, + ghost_smtp_from_address_obj] ) # admin credentials for ghost itself From c87b22d28a2b9de1c2bb9dc780b3c4a56b903e27 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 29 Sep 2025 15:49:15 +0200 Subject: [PATCH 9/9] add pxc operator --- smol_k8s_lab/__init__.py | 2 ++ smol_k8s_lab/k8s_apps/__init__.py | 3 +++ smol_k8s_lab/k8s_apps/operators/__init__.py | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/smol_k8s_lab/__init__.py b/smol_k8s_lab/__init__.py index a67d04003..0c5230cc1 100755 --- a/smol_k8s_lab/__init__.py +++ b/smol_k8s_lab/__init__.py @@ -239,6 +239,7 @@ def main(config: str = "", apps.get('ingress_nginx', {}), apps.get('cert_manager', {}), apps.get('cnpg_operator', {}), + apps.get('pxc_operator', {}), apps['argo_cd'], SECRETS, bw) @@ -270,6 +271,7 @@ def main(config: str = "", apps.pop('minio_operator', {'enabled': False}), apps.pop('seaweedfs', {'enabled': False}), apps.pop('cnpg_operator', {'enabled': False}), + apps.pop('pxc_operator', {'enabled': False}), apps.pop('postgres_operator', {'enabled': False}), apps.pop('openbao', {'enabled': False}), bw) diff --git a/smol_k8s_lab/k8s_apps/__init__.py b/smol_k8s_lab/k8s_apps/__init__.py index 547f5d75e..8b6a9b240 100644 --- a/smol_k8s_lab/k8s_apps/__init__.py +++ b/smol_k8s_lab/k8s_apps/__init__.py @@ -155,6 +155,7 @@ def setup_base_apps(k8s_obj: K8s, ingress_dict: dict = {}, cert_manager_dict: dict = {}, cnpg_operator_dict: dict = {}, + pxc_operator_dict: dict = {}, argocd_dict: dict = {}, plugin_secrets: dict = {}, bw: BwCLI = None) -> ArgoCD: @@ -169,6 +170,7 @@ def setup_base_apps(k8s_obj: K8s, cilium_enabled = cilium_dict.get('enabled', False) ingress_nginx_enabled = ingress_dict.get('enabled', False) cnpg_operator_enabled = cnpg_operator_dict.get('enabled', False) + pxc_operator_enabled = pxc_operator_dict.get('enabled', False) argocd_enabled = argocd_dict.get('enabled', False) cert_manager_enabled = cert_manager_dict.get('enabled', False) argo_secrets_plugin_enabled = argocd_dict['argo']['directory_recursion'] @@ -177,6 +179,7 @@ def setup_base_apps(k8s_obj: K8s, metallb_enabled, cilium_enabled, cnpg_operator_enabled, + pxc_operator_enabled, argocd_enabled, argo_secrets_plugin_enabled) diff --git a/smol_k8s_lab/k8s_apps/operators/__init__.py b/smol_k8s_lab/k8s_apps/operators/__init__.py index 5b82e69fd..2e4eaaaff 100644 --- a/smol_k8s_lab/k8s_apps/operators/__init__.py +++ b/smol_k8s_lab/k8s_apps/operators/__init__.py @@ -15,6 +15,7 @@ def setup_operators(argocd: ArgoCD, cnpg_config: dict = {}, pg_config: dict = {}, openbao_config: dict = {}, + pxc_config: dict = {}, bitwarden: BwCLI = None) -> None: """ deploy all k8s operators that can block other apps: @@ -57,6 +58,10 @@ def setup_operators(argocd: ArgoCD, if cnpg_config and cnpg_config.get('enabled', False): argocd.install_app('cnpg-operator', cnpg_config['argo']) + # pxc operator is a mysql operator for creating mysql clusters + if pxc_config and pxc_config.get('enabled', False): + argocd.install_app('pxc-operator', pxc_config['argo']) + # zalando postgres operator is a postgres operator for creating postgresql clusters if pg_config and pg_config.get('enabled', False): configure_postgres_operator(argocd, pg_config, bitwarden)