Headlamp's BASE_URL config expects a path prefix (e.g., "/headlamp") or empty
string, not a full URL. Since Headlamp is deployed at the domain root, BASE_URL
should be empty.
The OIDC redirect URIs in Pocket ID are correctly configured and the user has
been added to the headlamp group, which should resolve the authentication issue.
Related to DEV-324
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Set HEADLAMP_CONFIG_BASE_URL to https://headlamp.basicstack.de (was empty)
- Fix environment variable names to use underscores (OIDC_IDP_ISSUER_URL, etc.)
- Update image version to v0.43.0 to match deployed version
The missing BASE_URL caused the OIDC redirect flow to fail, resulting in the
auth popup closing immediately. Environment variable name corrections ensure
consistency with Headlamp's expected configuration format.
Resolves DEV-324
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add clear documentation for both OIDC and token-based authentication.
Include troubleshooting section for common 403 errors caused by
using tokens from wrong namespace.
Fixes issue where token was generated from kube-system instead of
headlamp namespace, causing 403 errors on metrics API.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Deploy Headlamp v0.27.0 in dedicated namespace
- Configure OIDC authentication with Pocket ID
- Grant cluster-admin access via service account
- Expose via Traefik ingress at headlamp.basicstack.de
- Seal OIDC client credentials
- Enable access for andreas.leinen@basicstack.de and admin@basicstack.de
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Start new 7-day verification window: 2026-07-13 21:07 UTC → 2026-07-20 21:07 UTC
- CEO confirmed Web-UI SMTP and IMAP working
- Restarting verification per Option A (clean 7-day window)
- Baseline status: all services verified working
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Verified all infrastructure working correctly (SMTP/IMAP/LoadBalancer)
- Identified user SMTP issue as client configuration (port 143 vs 587)
- Documented proposed verification window restart (2026-07-13 to 2026-07-20)
- Original window had 2+ days with broken LoadBalancer (10.42.1.1 vs 178.105.17.239)
Related: DEV-300
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Investigation revealed that k3s ServiceLB uses hostPort bindings on nodes'
public IPs, not the misleading EXTERNAL-IP VIP (10.42.1.1) shown in kubectl.
External connectivity was working correctly the entire time. The confusion
was caused by misinterpreting the flannel pod network IP in the EXTERNAL-IP
field as meaning external traffic couldn't reach the service.
Verified all SMTP/IMAP ports responding correctly with proper Stalwart banners.
Related: DEV-235
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Root cause: ArgoCD continuously reverts the LoadBalancer services to use
externalTrafficPolicy: Cluster (the k8s default), causing k3s to assign
internal flannel VXLAN IPs (10.42.1.x) instead of the node public IPs.
With externalTrafficPolicy: Cluster, traffic can be routed to any node,
and k3s's service controller assigns the flannel overlay IPs. This breaks
external connectivity because those IPs are not routable from outside.
With externalTrafficPolicy: Local, traffic is only routed to pods on the
same node, and k3s assigns the node's actual public IP to the LoadBalancer.
This was the missing piece from the reliability hardening in commit b0f2acf.
Without this in git, any manual kubectl patch is reverted by ArgoCD sync.
Evidence: stalwart-smtp and stalwart-imap both showing LoadBalancer IPs:
10.42.1.1, 10.42.1.2, 10.42.1.3, 10.42.1.5 (internal flannel IPs)
Related: DEV-230, DEV-231, DEV-233, DEV-235
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Root cause: k3s ServiceLB assigns internal VIPs (10.42.1.x) that are not
publicly routable. External traffic to mail.basicstack.de cannot reach the
SMTP/IMAP services.
Investigation shows:
- Web-UI works (goes through Traefik IngressRoute)
- SMTP/IMAP ports have correct firewall rules
- iptables DNAT rules exist but don't help external traffic
- Internal connectivity works correctly
- LoadBalancer 'external IPs' are actually pod network IPs
Three solution options documented:
A. NodePort + iptables REDIRECT (recommended)
B. Deploy MetalLB for true LoadBalancer IPs
C. Hetzner Cloud LoadBalancers (not recommended, cost)
Awaiting approval on approach before implementing fix.
Related: DEV-235
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This document establishes operational rules and diagnostic playbooks for
Stalwart stability monitoring and incident response.
Key sections:
- Critical operational rules (repository as single point of truth)
- Requirement to read git history before any investigation
- Current 7-day stability check procedures (DEV-235)
- Known issues and their fixes (k3s networking, commit b0f2acf)
- Diagnostic playbook for SMTP/IMAP/Web-UI issues
- Issue documentation template
- Evidence requirements for verification completion
This ensures all future investigations follow documented procedures and
avoid repeated debugging cycles.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Configure Argo CD to automatically sync bookstack from the repository,
ensuring the repository remains the single source of truth.
Changes:
- Added automated sync policy with prune and selfHeal
- prune: removes resources no longer in repo
- selfHeal: reverts manual cluster changes back to repo state
This resolves the manual sync requirement and ensures continuous
synchronization without data loss.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The mysql-data-encrypted PVC exists in the cluster and is working correctly,
but attempting to manage it through Argo CD causes sync failures because the
PVC is already bound and its spec is immutable. Leaving it unmanaged while
documenting its existence.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The MySQL deployment was referencing a PVC that wasn't defined in the
manifests. This PVC exists in the cluster but wasn't tracked by Argo CD.
Adding the definition ensures Argo CD can properly manage all resources
and prevents sync issues.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds the sealedsecrets.bitnami.com/managed: "true" label to all three
argocd sealed secrets (argocd-oidc-secret, repo-basicstack-org,
repo-stack-basicstack-de) to fix Health Degraded status in Argo CD.
This follows the same fix pattern applied in DEV-291, DEV-290, and DEV-289.
Closes DEV-293
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This tells Argo CD to ignore the unsealed secret created by the sealed-secrets controller, preventing the health status from being degraded.
This is the same fix applied in DEV-289 and DEV-290 for Forgejo and Argo.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Documents the complete migration process, safety measures, and verification steps
for the Pocket ID Argo CD migration.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Migrated Pocket ID deployment from manual kubectl to GitOps. All existing resources
will be adopted by Argo CD without recreation, preserving data and configuration.
Key safety measures:
- PVC has Delete=false annotation to prevent data loss
- Existing sealed secret reused for ENCRYPTION_KEY
- TLS certificate managed by cert-manager (not in Git)
- All OIDC clients and user data preserved in existing PVC
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds sealedsecrets.bitnami.com/managed annotation to ensure proper
lifecycle management by the sealed-secrets controller. This is a
non-destructive fix that improves secret synchronization without
touching encrypted data or affecting access.
Related: DEV-290, DEV-289
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Migrate all Forgejo deployment resources from cluster to Git repository:
- Forgejo Deployment with PostgreSQL backend
- Forgejo Service and Ingress with TLS
- Forgejo PersistentVolumeClaim (5Gi)
- PostgreSQL StatefulSet and Service
- Backup CronJob (daily at 03:00 UTC, 14-day retention)
All existing PVCs (forgejo-data, postgres-data-forgejo-postgres-0) and
Pocket-ID SSO configuration are preserved. No data loss expected.
The Argo CD Application (app-forgejo.yaml) is already configured to
sync from apps/forgejo/ path in this repository.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This adds the sealedsecrets.bitnami.com/managed: "true" label to the
mysql sealed secret to fix Health Degraded status in Argo CD.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The deployment was using client ID "bookstack" which does not exist in
Pocket ID. Updated to use the actual client ID from Pocket ID.
This fixes the "Record not found" error when attempting to login via
Pocket ID.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Fix Ingress rule host from bookstack.basicstack.de to books.basicstack.de
- Update OIDC issuer from pocket-id.basicstack.de to auth.basicstack.de
- Enable AUTH_AUTO_INITIATE for seamless SSO
- Update OIDC_NAME from "Pocket-ID" to "Pocket ID"
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Updated APP_URL environment variable and Ingress configuration to use the correct hostname books.basicstack.de.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Change MySQL from StatefulSet to Deployment to match existing setup
- Use existing PVC mysql-data-encrypted (contains live data) instead of creating new mysql-data
- Add PersistentVolumeClaim for bookstack-config to persist APP_KEY and configuration
- Remove duplicate MySQL resource definitions
- Align with current working deployment while making Forgejo the source of truth
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Added comprehensive Kubernetes deployment for Bookstack including:
- MySQL StatefulSet with PersistentVolumeClaim (10Gi encrypted storage)
- MySQL Service (ClusterIP)
- Bookstack Deployment with OIDC/Pocket-ID integration
- Bookstack Service (ClusterIP)
- Ingress with TLS certificate (bookstack.basicstack.de)
The deployment uses existing sealed secrets for MySQL credentials and
Bookstack OIDC client secret. Bookstack is configured with proper
health checks and resource limits.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The old recursive directory sync has been replaced with individual
Application manifests. The Application resource was deleted from the
cluster and this file is no longer needed.
Related: DEV-287
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Migrated from single recursive directory sync to individual Application
manifests for better control and visibility.
Changes:
- Created app-*.yaml for each application (10 apps)
- Disabled auto-sync on all apps for safe migration
- Disabled old app-stack-basicstack-de.yaml complex sync
- Updated app-argocd.yaml to disable auto-sync during migration
- Added ARGOCD-MIGRATION.md with migration plan and next steps
- Updated apps/README.md to document new structure
Each app now needs:
1. Manual sync verification
2. Health check
3. Auto-sync enablement (tracked in separate tasks)
Related: DEV-287
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Re-sealed the three Argo CD secrets with namespace: argocd instead of
namespace: paperclip to match the kustomization.yaml deployment target.
The sealed-secrets controller was failing to unseal these because they
were encrypted for the 'paperclip' namespace but Kustomize was deploying
them to the 'argocd' namespace. Sealed secrets use strict scope by
default, so namespace/name must match exactly.
Fixed files:
- apps/argocd/argocd-oidc-secret-sealed.yaml
- apps/argocd/repo-basicstack-org-secret-sealed.yaml
- apps/argocd/repo-stack-basicstack-de-secret-sealed.yaml
Root cause: DEV-284 investigation revealed controller error logs showing
"no key could decrypt secret" for all three Argo CD sealed secrets.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Created detailed documentation covering:
- Authentication and SSO login flow
- Adding new applications (Git and UI methods)
- Repository credential rotation procedure
- Emergency recovery procedures (with critical safety warnings)
- Self-management architecture
- Monitoring, maintenance, and troubleshooting
- Backup and disaster recovery
Updated README.md with Argo CD section and links to docs.
Updated docs/README.md to index the new Argo CD documentation.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
When Argo CD manages its own install manifest, it overwrites the live
argocd-ssh-known-hosts-cm with the upstream defaults (GitHub/GitLab/etc),
losing the Forgejo internal service key. Add the forgejo.forgejo.svc.cluster.local
host key so self-management doesn't break repo access after each sync.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Argo CD CRDs exceed the 262144-byte annotation limit that client-side
apply imposes via last-applied-configuration. Server-side apply bypasses
this limit and is required for Argo CD to manage its own large CRDs.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The argocd-cm document had two separate data: sections — one at the top
with resource customizations and one at the bottom with url/oidc.config.
This is invalid YAML and caused kustomize build to fail with
"mapping key data already defined". Merged url and oidc.config into the
single data: block to fix the argocd Application sync error.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Removing the exclusion caused the root app to apply argocd raw manifests
directly (bypassing kustomize), breaking namespace resolution. The correct
self-management design is: app-argocd.yaml Application is picked up by the
root app, which then delegates to the argocd Application managing apps/argocd/
via kustomize. The exclusion of argocd/** prevents double-applying resources.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Remove the 'argocd/**' exclusion from the root stack Application so that
Argo CD manages its own configuration via the app-of-apps pattern.
The apps/argocd/ directory already contains all manifests (install, ingress,
sealed secrets) and app-argocd.yaml defines the dedicated argocd Application.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Move metadata.name and app.kubernetes.io/part-of label from data section
to proper metadata section. This was causing kustomize build failures.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Change from multiline exclude with **/ pattern to simple single-line
'argocd/**' pattern relative to apps/ path.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Move all Application manifests (app-*.yaml) out of apps/argocd/ to apps/
to avoid chicken-and-egg issue where Applications couldn't update themselves.
Architecture:
- apps/app-stack-basicstack-de.yaml: manages apps/** excluding argocd/**
- apps/app-argocd.yaml: manages apps/argocd/** via kustomize
- apps/app-basicstack-org.yaml: manages basicstack.org repo
This enables full self-management: all Applications can sync their own
configurations from git.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Fully exclude argocd directory from stack Application to prevent
namespace errors. The argocd Application (apps/app-argocd.yaml) handles
all argocd resources via kustomize.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add test annotation to argocd-cm ConfigMap to verify that Argo CD
automatically syncs changes to its own configuration.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This allows the stack Application to manage Argo CD's own configuration
via GitOps, implementing the app-of-apps pattern.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Creates the Argo CD Application that manages all cluster infrastructure
applications from the stack.basicstack.de repository. Configuration:
- Points to cluster-internal Forgejo (forgejo.forgejo.svc.cluster.local)
- Excludes argocd directory to prevent self-management
- Enables selfHeal for automatic sync of changes
- Disables prune for manual deletion approval
- Deployed to the cluster and verified working
All existing services (Stalwart, Directus, Forgejo, BookStack, Pocket ID,
OpenCloud, Passbolt) remain operational. Some pods restarted due to applying
pending git changes (expected GitOps behavior).
Note: Warning about duplicate opencloud namespace definition in
openldap-deployment.yaml and opencloud-deployment.yaml - does not affect
functionality, can be addressed in follow-up cleanup.
Co-Authored-By: Paperclip <noreply@paperclip.ing>