Compare commits
No commits in common. "new-core-structure" and "master" have entirely different histories.
new-core-s
...
master
37 changed files with 76 additions and 385 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,5 +1,2 @@
|
||||||
**/*/.envs/.*
|
.envs/.*
|
||||||
!**/*/.envs/.*.template
|
!.envs/.*.template
|
||||||
|
|
||||||
stages/.common/*
|
|
||||||
!stages/.common/*.template
|
|
30
README.md
30
README.md
|
@ -1,25 +1,19 @@
|
||||||
# gitops
|
# gitops
|
||||||
This repository contains three level configration of core services
|
This repository contains core services for my kubernetes cluster which are
|
||||||
|
|
||||||
## Stage 1 - `base` stage
|
|
||||||
These services are needed to run the cluster in general
|
|
||||||
- MetalLB - LoadBalancer
|
- MetalLB - LoadBalancer
|
||||||
- Ingress Nginx - Ingress Controller
|
- Ingress Nginx - Ingress Controller
|
||||||
- CSI Drifer NFS - PVC
|
- CSI NFS - PVC
|
||||||
- PiHole and ExternalDNS - LAN DNS
|
- PiHole and ExternalDNS - LAN DNS
|
||||||
|
|
||||||
## Stage 2 - `identity` stage
|
|
||||||
These services are needed to run all other core services in stage 3
|
|
||||||
|
|
||||||
- VaultWarden - Password and secret management - TODO: create chart or using Kustomize
|
|
||||||
- Authentik - SSO and auth provider for the whole cluster
|
|
||||||
|
|
||||||
## Stage 3 - `delivery` stage
|
|
||||||
All other core services with auth or secrets
|
|
||||||
|
|
||||||
- ArgoCD - GitOps for my other services
|
- ArgoCD - GitOps for my other services
|
||||||
- Forgejo - Repository for ArgoCD and all of my other projects
|
## How to use
|
||||||
|
|
||||||
# How to use
|
1. Get the secrets
|
||||||
|
Either manually put your secrets in .env or run `./scripts/bw2secrets` - TODO
|
||||||
|
|
||||||
## Stage 1
|
2. Apply Kustomizations
|
||||||
|
`kubectl apply -k .`
|
||||||
|
|
||||||
|
3. Install all the apps
|
||||||
|
`helmfile apply`
|
||||||
|
|
||||||
|
4. Profit!
|
|
@ -3,8 +3,12 @@ repositories:
|
||||||
url: https://metallb.github.io/metallb
|
url: https://metallb.github.io/metallb
|
||||||
- name: ingress-nginx
|
- name: ingress-nginx
|
||||||
url: https://kubernetes.github.io/ingress-nginx
|
url: https://kubernetes.github.io/ingress-nginx
|
||||||
|
- name: csi-driver-nfs
|
||||||
|
url: https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
|
||||||
- name: jetstack
|
- name: jetstack
|
||||||
url: https://charts.jetstack.io
|
url: https://charts.jetstack.io
|
||||||
|
- name: argocd
|
||||||
|
url: https://argoproj.github.io/argo-helm
|
||||||
- name: mojo2600
|
- name: mojo2600
|
||||||
url: https://mojo2600.github.io/pihole-kubernetes/
|
url: https://mojo2600.github.io/pihole-kubernetes/
|
||||||
- name: bitnami
|
- name: bitnami
|
||||||
|
@ -21,18 +25,22 @@ releases:
|
||||||
version: 4.12.0
|
version: 4.12.0
|
||||||
values:
|
values:
|
||||||
- ./values/ingress-nginx.values.yaml
|
- ./values/ingress-nginx.values.yaml
|
||||||
- name: proxmox-csi-plugin
|
- name: csi-driver-nfs
|
||||||
namespace: proxmox-csi
|
namespace: kube-system
|
||||||
chart: oci://ghcr.io/sergelogvinov/charts/proxmox-csi-plugin
|
chart: csi-driver-nfs/csi-driver-nfs
|
||||||
version: 0.3.5
|
version: v4.9.0
|
||||||
values:
|
|
||||||
- ./values/proxmox-csi-plugin.values.yaml.gotmpl
|
|
||||||
- name: cert-manager
|
- name: cert-manager
|
||||||
namespace: cert-manager
|
namespace: cert-manager
|
||||||
chart: jetstack/cert-manager
|
chart: jetstack/cert-manager
|
||||||
version: v1.16.2
|
version: v1.16.2
|
||||||
values:
|
values:
|
||||||
- ./values/cert-manager.values.yaml
|
- ./values/cert-manager.values.yaml
|
||||||
|
- name: argocd
|
||||||
|
namespace: argocd
|
||||||
|
chart: argocd/argo-cd
|
||||||
|
version: 7.7.21
|
||||||
|
values:
|
||||||
|
- ./values/argocd.values.yaml
|
||||||
- name: pihole
|
- name: pihole
|
||||||
namespace: pihole
|
namespace: pihole
|
||||||
chart: mojo2600/pihole
|
chart: mojo2600/pihole
|
|
@ -3,9 +3,8 @@ kind: Kustomization
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ./kustomize/metallb.yaml
|
- ./kustomize/metallb.yaml
|
||||||
|
- ./kustomize/csi-driver-nfs.yaml
|
||||||
- ./kustomize/cert-manager-cloudflare.yaml
|
- ./kustomize/cert-manager-cloudflare.yaml
|
||||||
- ./kustomize/proxmox-csi-namespace.yaml
|
|
||||||
- ./kustomize/coredns-resolv-fix.yaml
|
|
||||||
|
|
||||||
|
|
||||||
secretGenerator:
|
secretGenerator:
|
13
kustomize/csi-driver-nfs.yaml
Normal file
13
kustomize/csi-driver-nfs.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: nfs-csi
|
||||||
|
annotations:
|
||||||
|
storageclass.kubernetes.io/is-default-class: "true"
|
||||||
|
provisioner: nfs.csi.k8s.io
|
||||||
|
parameters:
|
||||||
|
server: 192.168.1.180
|
||||||
|
share: /mnt/nas
|
||||||
|
reclaimPolicy: Delete
|
||||||
|
volumeBindingMode: Immediate
|
||||||
|
allowVolumeExpansion: true
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "82043f19-dfa4-4c95-9720-24609d12fedd" }}
|
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "bf0cc098-bbf1-4254-9133-40024505c093" }}
|
|
|
@ -1,11 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: coredns-custom
|
|
||||||
namespace: kube-system
|
|
||||||
data:
|
|
||||||
kropcloud.net.server: |
|
|
||||||
kropcloud.net {
|
|
||||||
log
|
|
||||||
forward . 192.168.1.250
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: proxmox-csi
|
|
||||||
labels:
|
|
||||||
pod-security.kubernetes.io/enforce: privileged
|
|
||||||
pod-security.kubernetes.io/audit: baseline
|
|
||||||
pod-security.kubernetes.io/warn: baseline
|
|
|
@ -1,16 +0,0 @@
|
||||||
config:
|
|
||||||
clusters:
|
|
||||||
- url: https://192.168.1.151:8006/api2/json
|
|
||||||
insecure: true
|
|
||||||
token_id: "kubernetes-csi@pve!csi"
|
|
||||||
token_secret: {{ readFile "../.envs/.proxmox-csi-secret" }}
|
|
||||||
region: KropCloud
|
|
||||||
|
|
||||||
storageClass:
|
|
||||||
- name: proxmox-data
|
|
||||||
storage: hdd-data1
|
|
||||||
reclaimPolicy: Delete
|
|
||||||
fstype: ext4
|
|
||||||
cache: writethrough
|
|
||||||
annotations:
|
|
||||||
storageclass.kubernetes.io/is-default-class: "true"
|
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "46289080-39de-4e5e-bae5-6be41b08e25b" }}
|
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "1a13caa1-547e-4462-af34-8dff29baec64" }}
|
|
|
@ -1,18 +0,0 @@
|
||||||
repositories:
|
|
||||||
- name: argocd
|
|
||||||
url: https://argoproj.github.io/argo-helm
|
|
||||||
---
|
|
||||||
|
|
||||||
releases:
|
|
||||||
- name: argocd
|
|
||||||
namespace: argocd
|
|
||||||
chart: argocd/argo-cd
|
|
||||||
version: 7.7.21
|
|
||||||
values:
|
|
||||||
- ./values/argocd.values.yaml.gotmpl
|
|
||||||
- name: forgejo
|
|
||||||
namespace: forgejo
|
|
||||||
chart: oci://code.forgejo.org/forgejo-helm/forgejo
|
|
||||||
version: 11.0.3
|
|
||||||
values:
|
|
||||||
- ./values/forgejo.values.yaml.gotmpl
|
|
|
@ -1,57 +0,0 @@
|
||||||
global:
|
|
||||||
domain: argo.kropcloud.net
|
|
||||||
|
|
||||||
configs:
|
|
||||||
secret:
|
|
||||||
extra:
|
|
||||||
dex.kropcloud-idp.clientSecret: {{ readFile "../.envs/.argocd-oidc-secret" }}
|
|
||||||
|
|
||||||
params:
|
|
||||||
server.insecure: true
|
|
||||||
|
|
||||||
cm:
|
|
||||||
dex.config: |
|
|
||||||
connectors:
|
|
||||||
- id: authentik
|
|
||||||
type: oidc
|
|
||||||
name: KropCloud IDP
|
|
||||||
config:
|
|
||||||
issuer: https://idp.kropcloud.net/application/o/argocd/
|
|
||||||
clientID: R6KnCiwgsevzTkWhB9dopV80sHxL8kS4QjVlMmqI
|
|
||||||
clientSecret: $dex.kropcloud-idp.clientSecret
|
|
||||||
insecureEnableGroups: true
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
- email
|
|
||||||
- groups
|
|
||||||
rbac:
|
|
||||||
policy.csv: |
|
|
||||||
g, ArgoCD Admins, role:admin
|
|
||||||
|
|
||||||
redis-ha:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
controller:
|
|
||||||
replicas: 1
|
|
||||||
|
|
||||||
server:
|
|
||||||
replicas: 2
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
ingressClassName: nginx
|
|
||||||
annotations:
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
|
|
||||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- argo.kropcloud.net
|
|
||||||
secretName: argocd-tls
|
|
||||||
|
|
||||||
|
|
||||||
repoServer:
|
|
||||||
replicas: 2
|
|
||||||
|
|
||||||
applicationSet:
|
|
||||||
replicas: 2
|
|
|
@ -1,70 +0,0 @@
|
||||||
redis-cluster:
|
|
||||||
enabled: false
|
|
||||||
redis:
|
|
||||||
enabled: true
|
|
||||||
postgresql:
|
|
||||||
enabled: true
|
|
||||||
postgresql-ha:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
gitea:
|
|
||||||
oauth:
|
|
||||||
- name: kropcloud-idp
|
|
||||||
provider: openidConnect
|
|
||||||
key: VcyEM48aqaMlau356WMVO10cNcmd6McnxW1KvBLu
|
|
||||||
secret: {{ readFile "../.envs/.forgejo-oidc-secret" }}
|
|
||||||
autoDiscoverUrl: https://idp.kropcloud.net/application/o/git/.well-known/openid-configuration
|
|
||||||
skipLocal2fa: false
|
|
||||||
scopes: forgejo
|
|
||||||
requiredClaimName: forgejo
|
|
||||||
groupClaimName: forgejo
|
|
||||||
adminGroup: admin
|
|
||||||
|
|
||||||
|
|
||||||
config:
|
|
||||||
service:
|
|
||||||
DISABLE_REGISTRATION: false
|
|
||||||
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
|
|
||||||
oauth2_client:
|
|
||||||
ENABLE_AUTO_REGISTRATION: true
|
|
||||||
UPDATE_AVATAR: true
|
|
||||||
openid:
|
|
||||||
ENABLE_OPENID_SIGNIN: false
|
|
||||||
ENABLE_OPENID_SIGNUP: false
|
|
||||||
database:
|
|
||||||
DB_TYPE: postgres
|
|
||||||
indexer:
|
|
||||||
ISSUE_INDEXER_TYPE: bleve
|
|
||||||
REPO_INDEXER_ENABLED: true
|
|
||||||
mailer:
|
|
||||||
ENABLED: true
|
|
||||||
FROM: Forgejo <no-reply@kropcloud.net>
|
|
||||||
PROTOCOL: smtps
|
|
||||||
SMTP_ADDR: smtp.seznam.cz
|
|
||||||
SMTP_PORT: 465
|
|
||||||
USER: no-reply@kropcloud.net
|
|
||||||
PASSWD: {{ readFile "../../.common/.noreply-email-password" }}
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: nginx
|
|
||||||
hosts:
|
|
||||||
- host: git.kropcloud.net
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
annotations:
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
nginx.ingress.kubernetes.io/baWckend-protocol: "HTTP"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
|
||||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- git.kropcloud.net
|
|
||||||
secretName: forgejo-tls
|
|
||||||
|
|
||||||
service:
|
|
||||||
ssh:
|
|
||||||
type: LoadBalancer
|
|
||||||
annotations:
|
|
||||||
metallb.io/allow-shared-ip: kropcloud
|
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "bdf24fa1-8638-4cd1-a17a-df5f0bc8adee" }}
|
|
|
@ -1 +0,0 @@
|
||||||
{{ pw "0e694c6c-9b5c-48c5-b884-6f7274c74832" }}
|
|
|
@ -1 +0,0 @@
|
||||||
admin-token=16a6b142-bb39-4708-9de1-14157fee29d3
|
|
|
@ -1,11 +0,0 @@
|
||||||
repositories:
|
|
||||||
- name: authentik
|
|
||||||
url: https://charts.goauthentik.io/
|
|
||||||
---
|
|
||||||
releases:
|
|
||||||
- name: authentik
|
|
||||||
namespace: authentik
|
|
||||||
chart: authentik/authentik
|
|
||||||
version: 2024.12.3
|
|
||||||
values:
|
|
||||||
- ./values/authentik.values.yaml.gotmpl
|
|
|
@ -1,13 +0,0 @@
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
metadata:
|
|
||||||
name: identity
|
|
||||||
|
|
||||||
secretGenerator:
|
|
||||||
- name: vaultwarden-secret
|
|
||||||
namespace: vaultwarden
|
|
||||||
envs:
|
|
||||||
- .envs/.vaultwarden-admin-token
|
|
||||||
|
|
||||||
resources:
|
|
||||||
- ./resources/vaultwarden/
|
|
|
@ -1,36 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: vaultwarden
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: vaultwarden
|
|
||||||
spec:
|
|
||||||
volumes:
|
|
||||||
- name: vaultwarden-pvc
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: vaultwarden-pvc
|
|
||||||
containers:
|
|
||||||
- name: vaultwarden
|
|
||||||
image: vaultwarden/server
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 256Mi
|
|
||||||
cpu: 500m
|
|
||||||
ports:
|
|
||||||
- name: vw-http
|
|
||||||
containerPort: 80
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /data
|
|
||||||
name: vaultwarden-pvc
|
|
||||||
env:
|
|
||||||
- name: ADMIN_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: admin-token
|
|
||||||
name: vaultwarden-secret
|
|
|
@ -1,18 +0,0 @@
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden-ingress
|
|
||||||
labels:
|
|
||||||
name: vaultwarden-ingress
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: pass.kropcloud.net
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- pathType: Prefix
|
|
||||||
path: /
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: vaultwarden-svc
|
|
||||||
port:
|
|
||||||
number: 80
|
|
|
@ -1,20 +0,0 @@
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
namespace: vaultwarden
|
|
||||||
|
|
||||||
labels:
|
|
||||||
- pairs:
|
|
||||||
app.kubernetes.io/managed-by: Kustomize
|
|
||||||
app.kubernetes.io/part-of: vaultwarden
|
|
||||||
app.kubernetes.io/version: 1.33.2
|
|
||||||
|
|
||||||
resources:
|
|
||||||
- ./deployment.yaml
|
|
||||||
- ./pvc.yaml
|
|
||||||
- ./service.yaml
|
|
||||||
- ./ingress.yaml
|
|
||||||
- ./namespace.yaml
|
|
||||||
|
|
||||||
images:
|
|
||||||
- name: vaultwarden/server
|
|
||||||
newTag: 1.33.2
|
|
|
@ -1,4 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
|
@ -1,11 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden-pvc
|
|
||||||
spec:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 10Gi
|
|
||||||
volumeMode: Filesystem
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
|
@ -1,11 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden-svc
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: vaultwarden
|
|
||||||
ports:
|
|
||||||
- name: vaultwarden-http-svc
|
|
||||||
port: 80
|
|
||||||
targetPort: vw-http
|
|
|
@ -1,38 +0,0 @@
|
||||||
postgresql:
|
|
||||||
enabled: true
|
|
||||||
auth:
|
|
||||||
password: {{ readFile "../.envs/.authentik-postgresql" }}
|
|
||||||
volumePermissions:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
authentik:
|
|
||||||
secret_key: {{ readFile "../.envs/.authentik-secret-key" }}
|
|
||||||
|
|
||||||
email:
|
|
||||||
host: smtp.seznam.cz
|
|
||||||
port: 465
|
|
||||||
use_ssl: true
|
|
||||||
from: KropCloud IDP <no-reply@kropcloud.net>
|
|
||||||
username: no-reply@kropcloud.net
|
|
||||||
password: {{ readFile "../../.common/.noreply-email-password" }}
|
|
||||||
|
|
||||||
postgresql:
|
|
||||||
password: {{ readFile "../.envs/.authentik-postgresql" }}
|
|
||||||
|
|
||||||
redis:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
server:
|
|
||||||
ingress:
|
|
||||||
ingressClassName: nginx
|
|
||||||
enabled: true
|
|
||||||
hosts:
|
|
||||||
- idp.kropcloud.net
|
|
||||||
annotations:
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
|
||||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- idp.kropcloud.net
|
|
||||||
secretName: authentik-tls
|
|
33
values/argocd.values.yaml
Normal file
33
values/argocd.values.yaml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
global:
|
||||||
|
domain: argo.kropcloud.net
|
||||||
|
|
||||||
|
configs:
|
||||||
|
params:
|
||||||
|
server.insecure: true
|
||||||
|
|
||||||
|
redis-ha:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
controller:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
server:
|
||||||
|
replicas: 2
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
ingressClassName: nginx
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||||
|
cert-manager.io/cluster-issuer: cloudflare-issuer
|
||||||
|
extraTls:
|
||||||
|
- hosts:
|
||||||
|
- argo.kropcloud.net
|
||||||
|
secretName: argocd-tls
|
||||||
|
|
||||||
|
|
||||||
|
repoServer:
|
||||||
|
replicas: 2
|
||||||
|
|
||||||
|
applicationSet:
|
||||||
|
replicas: 2
|
|
@ -7,4 +7,4 @@ pihole:
|
||||||
secretName: pihole-admin
|
secretName: pihole-admin
|
||||||
|
|
||||||
ingressClassFilters:
|
ingressClassFilters:
|
||||||
- nginx
|
- ingress-nginx
|
|
@ -1,8 +1,4 @@
|
||||||
DNS1: 1.1.1.1
|
|
||||||
DNS2: 1.0.0.1
|
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
ingressClassName: nginx
|
|
||||||
enabled: true
|
enabled: true
|
||||||
hosts:
|
hosts:
|
||||||
- pihole.kropcloud.net
|
- pihole.kropcloud.net
|
Loading…
Reference in a new issue