feat: add Linkding deployment

This commit is contained in:
Daniel Cosme
2026-04-17 20:49:37 -04:00
parent ef85cf4d7e
commit 573e8b6670
14 changed files with 221 additions and 18 deletions
+22
View File
@@ -1 +1,23 @@
# GitOps # GitOps
A set of "best practices" where the entire code delivery process is controlled via Git, including infrastructure and application definition as code and automation to complete updates and rollbacks.
The key GitOps Principles:
- The entire system (infrastructure and applications) is described declaratively.
- The canonical desired system state is versioned in Git.
- Changes approved are automated and applied to the system.
- Software agents ensure correctness and alert on divergence.
https://opengitops.dev/
Key points for a Kubernetes cluster:
The state of the cluster is always described in Git. Git holds everything for the application and not just the source code.
There is no external deployment system with full access to the cluster. The cluster itself is pulling changes and deployment information.
The GitOps controller is running in a constant loop and always matches the Git state with the cluster state (reconciliation loop).
flux vs ArgoCD
Flux is more CLI Driven ArgoCD has a richer GUI
## Flux
Prerequisites:
- Kubernetes Cluster
-Github (giea, etc) personal access token
+24
View File
@@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: linking
name: linking
namespace: linkding
spec:
selector:
matchLabels:
app: linking
strategy: {}
template:
metadata:
labels:
app: linking
spec:
containers:
- image: sissbruecket/linkding:1.31.0
name: linkding
ports:
- containerPort: 9090
resources: {}
status: {}
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: linkding
spec: {}
status: {}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: linking
name: linking
namespace: linkding
spec:
ports:
- port: 9090
targetPort: 0
selector:
app: linking
status:
loadBalancer: {}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
path: ./apps/hydra
prune: true
retryInterval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
timeout: 5m0s
status: {}
+1 -1
View File
@@ -3,7 +3,7 @@ module danicos.dev/daniel/homelab
go 1.26.2 go 1.26.2
require ( require (
danicos.dev/daniel/go-kube v1.2.0 danicos.dev/daniel/go-kube v1.2.2
github.com/fatih/color v1.19.0 github.com/fatih/color v1.19.0
github.com/fluxcd/kustomize-controller/api v1.8.3 github.com/fluxcd/kustomize-controller/api v1.8.3
github.com/magefile/mage v1.17.1 github.com/magefile/mage v1.17.1
+2 -2
View File
@@ -1,5 +1,5 @@
danicos.dev/daniel/go-kube v1.2.0 h1:nLcpSOmszmkMnHd4Fb9V7R8qO7z2OwNgUFFWQNwQazs= danicos.dev/daniel/go-kube v1.2.2 h1:RW/mzIMIyMCEftTyvRTeJmo44JaCDsxLMlZcEnZxN1g=
danicos.dev/daniel/go-kube v1.2.0/go.mod h1:FLgPxaiDpLmIwWddx5x4jTfP/onTzkOLbhmpdt2uX6w= danicos.dev/daniel/go-kube v1.2.2/go.mod h1:FLgPxaiDpLmIwWddx5x4jTfP/onTzkOLbhmpdt2uX6w=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+3 -3
View File
@@ -13,9 +13,9 @@ import (
*/) */)
var r target.Runner var r target.Runner
var Default = Run var Default = Build
var Aliases = map[string]any{ var Aliases = map[string]any{
"r": Run, "b": Build,
} }
func init() { func init() {
@@ -28,7 +28,7 @@ func init() {
r = target.NewRunner(Env, nil) r = target.NewRunner(Env, nil)
} }
func Run() error { func Build() error {
t := target.NewA("go", "run", ".") t := target.NewA("go", "run", ".")
return r.RunV("run", t) return r.RunV("run", t)
} }
+30 -8
View File
@@ -1,15 +1,37 @@
package main package main
import ( import (
"danicos.dev/daniel/go-kube/pkg/kube" "fmt"
"os"
"danicos.dev/daniel/go-kube/pkg/stack"
"danicos.dev/daniel/homelab/pkg/flux" "danicos.dev/daniel/homelab/pkg/flux"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" "danicos.dev/daniel/homelab/pkg/linkding"
) "danicos.dev/daniel/homelab/pkg/root"
/*
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
net "k8s.io/api/networking/v1"
*/)
func main() { func main() {
meta := kube.NewMetadata("name", kube.Namespace("nae")) flux_stack := flux.Stack()
kuz := kube.NewKustomization(meta, kustomizev1.KustomizationSpec{}) err := flux_stack.MarshalYamlFlat(root.FLUX_CLUSTER_HYDRA_PATH)
fluxStack := flux.Stack() assertNoErr(err)
fluxStack.Add("apps", kuz)
fluxStack.MarshalYaml("") hydraApps := map[string]stack.Stack{
"linkding": linkding.Stack(),
}
for name, s := range hydraApps {
fmt.Printf("STACK: %s\n", name)
err = s.MarshalYaml(root.FLUX_APPS_HYDRA_PATH)
assertNoErr(err)
}
}
func assertNoErr(err error) {
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
} }
+40 -2
View File
@@ -1,8 +1,46 @@
package flux package flux
import "danicos.dev/daniel/go-kube/pkg/stack" import (
"time"
"danicos.dev/daniel/go-kube/pkg/kube"
"danicos.dev/daniel/go-kube/pkg/stack"
"danicos.dev/daniel/homelab/pkg/root"
kz "github.com/fluxcd/kustomize-controller/api/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var flux_apps_meta kube.Metadata
var Flux_namespace = kube.Namespace(root.FLUX_NAMESPACE)
func init() {
flux_apps_meta = kube.NewMetadata("apps", Flux_namespace)
}
func Stack() stack.Stack { func Stack() stack.Stack {
s := stack.NewStack("", nil) s := stack.NewStack("flux", map[string]any{
"apps": Apps(),
})
return s return s
} }
func Apps() kz.Kustomization {
retryInteval := durMin(1)
timeout := durMin(5)
spec := kz.KustomizationSpec{
Interval: durMin(10),
RetryInterval: &retryInteval,
Timeout: &timeout,
SourceRef: kz.CrossNamespaceSourceReference{
Kind: "GitRepository",
Name: Flux_namespace.Name,
},
Path: root.FLUX_APPS_HYDRA_PATH,
Prune: true,
}
return kube.NewKustomization(flux_apps_meta, spec)
}
func durMin(d int64) meta.Duration {
return meta.Duration{Duration: (time.Duration(d) * time.Minute)}
}
+42
View File
@@ -0,0 +1,42 @@
package linkding
import (
"danicos.dev/daniel/go-kube/pkg/kube"
"danicos.dev/daniel/go-kube/pkg/stack"
"danicos.dev/daniel/homelab/pkg/root"
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
)
var meta kube.Metadata
var Namespace = kube.Namespace("linkding")
var srv core.Service
func init() {
meta = kube.NewMetadata("linking", Namespace)
srv = meta.Service(root.Linkding.Port)
}
func Stack() stack.Stack {
s := stack.NewStack("linkding", map[string]any{
"namespace": Namespace,
"srv": srv,
"deployment": deployment(),
})
return s
}
func deployment() apps.Deployment {
pod_spec := core.PodSpec{
Containers: []core.Container{
{
Name: root.Linkding.Name,
Image: root.Linkding.Image,
Ports: []core.ContainerPort{{
ContainerPort: root.Linkding.Port,
}},
},
},
}
return kube.NewDeployment(meta, pod_spec)
}
+7 -1
View File
@@ -1,7 +1,13 @@
package root package root
var ( const (
HYDRA_CLUSTER = "hydra" HYDRA_CLUSTER = "hydra"
HYDRA_HOSTNAME = "hydra-0" // VPN Host HYDRA_HOSTNAME = "hydra-0" // VPN Host
GITEA_HOST = "danicos.dev" GITEA_HOST = "danicos.dev"
) )
const (
FLUX_NAMESPACE = "flux-system"
FLUX_APPS_HYDRA_PATH = "./apps/" + HYDRA_CLUSTER
FLUX_CLUSTER_HYDRA_PATH = "./clusters/" + HYDRA_CLUSTER
)
+13
View File
@@ -0,0 +1,13 @@
package root
type Service struct {
Name string
Image string
Port int32
}
var Linkding = Service{
Name: "linkding",
Image: "sissbruecket/linkding:1.31.0",
Port: 9090,
}
+1 -1
View File
@@ -13,4 +13,4 @@ flux --kubeconfig ~/.kube/$CLUSTER_NAME \
--private=false \ --private=false \
--branch=main \ --branch=main \
--personal=true \ --personal=true \
--path=./clusters/hydra --path=./clusters/$CLUSTER_NAME