Compare commits

...

63 commits

Author SHA1 Message Date
faeeb1c9f7 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/c4k-taiga 2024-08-06 13:14:57 +02:00
975ea81262 [Skip-CI] Add website to contact info 2024-08-06 13:14:51 +02:00
Clemens
9201aba498 make superuser creation manual 2024-06-03 10:05:22 +02:00
bom
f8abf9a414 Use correct image for clj build 2024-02-23 16:53:11 +01:00
bom
24b1abf7af Add native build 2024-02-23 16:47:24 +01:00
bom
1c3088630f Use inline-resources macro 2024-02-23 16:20:46 +01:00
bom
dbffa50dab Add generic test task to build 2024-02-23 16:11:32 +01:00
bom
424a7bee74 Refactor common yaml loading 2024-02-23 16:11:22 +01:00
bom
671c12b650 bump version to: 1.1.3-SNAPSHOT 2024-01-19 13:21:35 +01:00
bom
dfb5fd8f08 release: 1.1.2 2024-01-19 13:21:35 +01:00
bom
3ad9937ff4 Bump versions 2024-01-19 13:20:47 +01:00
c76f7e58f0 [skip-ci] infrastructure/../test folder removed 2023-12-22 16:55:49 +01:00
5682627606 lift versions ddadevops dind/clj/clj-cljs 2023-12-21 21:17:14 +01:00
13218b395e Improvements docker image building 2023-12-21 19:56:34 +01:00
ac3c1cdd3e [Skip-CI] Update Readme 2023-11-08 16:20:46 +01:00
c76e417e7a [Skip-CI] Update deps 2023-11-08 16:16:00 +01:00
f39fbea68b [Skip-CI] Ignore .eastwood file 2023-11-08 16:15:27 +01:00
77b385d0c2 bump version to: 1.1.2-SNAPSHOT 2023-11-08 15:37:29 +01:00
681f17ce6c release: 1.1.1 2023-11-08 15:37:29 +01:00
25c1083b3f bump version to: 1.1.1-SNAPSHOT 2023-11-08 15:07:41 +01:00
f3ffb8b824 release: 1.1.0 2023-11-08 15:07:41 +01:00
53b56b5cd6 Correct version to package not clojure 2023-11-08 15:07:05 +01:00
322caa4762 bump version to: 1.0.1-SNAPSHOT 2023-11-08 14:58:09 +01:00
ffcd34b632 release: 1.0.0 2023-11-08 14:58:09 +01:00
b0b8a8105a Use main as release branch 2023-11-08 14:57:31 +01:00
c06ff29ded Bump c4k-common version 2023-11-08 14:50:17 +01:00
5dab7a095f Use namespace predicate 2023-11-08 14:48:27 +01:00
8a06349544 Remove set -eux from non install scripts 2023-11-03 14:41:06 +01:00
94aee95c3e Update backup 2023-11-03 12:08:11 +01:00
a318514403 Update restore 2023-11-03 11:35:05 +01:00
c1e8913fdf fail on install error 2023-11-03 11:09:14 +01:00
31f418dddd use new backup base image 2023-11-03 11:09:11 +01:00
52642908e0 Use set -eux in scripts 2023-11-03 10:58:56 +01:00
674160cc25 [Skip-CI] Deprecate old release doc 2023-10-24 15:00:30 +02:00
e8cd30c53e [Skip-CI] Remove website related files 2023-10-24 14:59:44 +02:00
eed4ff0651 [Skip-CI] Rename reference config folder 2023-10-24 14:58:38 +02:00
9226433c01 [Skip-CI] Correct commands for backup-restore 2023-10-24 14:55:51 +02:00
581aadb8ae [Skip-CI] Move instead of copy 2023-10-24 14:41:01 +02:00
780ee0f4e2 Update deps 2023-10-19 14:37:33 +02:00
f1c3b9a021 Use pyb for CI 2023-10-19 14:37:15 +02:00
aa8c69bbc2 Add further details 2023-10-19 11:39:00 +02:00
6e3c522173 Update readme 2023-10-19 11:32:48 +02:00
fd5197d7df Add BackupAndRestore info 2023-10-19 10:47:44 +02:00
e4609eb510 Update backup section 2023-10-19 10:47:28 +02:00
4e27a7ada9 Rename to backup 2023-10-19 10:47:02 +02:00
725e25e362 Make media writable for restore 2023-10-19 10:17:18 +02:00
b2b7fae1aa Fix copy paths and use backup directory function 2023-10-19 10:16:43 +02:00
4628d1409c Use correct api for CronJobs 2023-10-18 14:32:01 +02:00
f60b113037 Generate application.yaml with backup 2023-10-18 14:14:24 +02:00
0df619fdca Remove erroneous docker image 2023-10-18 12:07:21 +02:00
ad7a41f0d7 Adapt restore to taiga 2023-10-18 12:03:39 +02:00
b753ba14cc Use backup-fs-from-directory instead of backup-directory 2023-10-18 11:46:10 +02:00
bfea9abb5f Mount correct volumes and use path in backup.sh 2023-10-18 11:36:08 +02:00
7d3e1ff03a Correct what's to back up 2023-10-18 11:35:04 +02:00
7e3ba30f57 Rename to taiga 2023-10-18 11:14:04 +02:00
206a860810 Start integrating backup from forgejo template 2023-10-18 11:05:16 +02:00
63ba3c41f9 Cleanup readme 2023-09-05 12:39:59 +02:00
bcdaf3e541 Implement browser 2023-09-05 12:36:40 +02:00
74b1c60fbe Fix tests 2023-09-05 12:36:18 +02:00
56047be200 Correct specs 2023-09-05 12:36:04 +02:00
ede3090ccc Update base64 2023-09-05 12:35:38 +02:00
6bb4e59a05 Cleanup readme 2023-09-05 12:34:19 +02:00
0d7b30fe82 Merge pull request 'Working Base Config for Taiga' (#1) from config-play into main
Reviewed-on: #1
2023-09-01 10:38:17 +00:00
51 changed files with 1091 additions and 1140 deletions

3
.gitignore vendored
View file

@ -1,3 +1,5 @@
.eastwood
.clj-kondo/
.lsp/
@ -10,6 +12,7 @@ target/
.lein-repl-history
.lein-failures
pom.*
reports/*
# cljs
.shadow-cljs

View file

@ -5,11 +5,18 @@ stages:
- upload
- image
services:
- docker:dind
.img: &img
image: "domaindrivenarchitecture/ddadevops-dind:4.11.3"
services:
- docker:dind
before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- export IMAGE_DOCKERHUB_USER=$DOCKERHUB_USER
- export IMAGE_DOCKERHUB_PASSWORD=$DOCKERHUB_PASSWORD
- export IMAGE_TAG=$CI_COMMIT_TAG
.cljs-job: &cljs
image: domaindrivenarchitecture/shadow-cljs
image: "domaindrivenarchitecture/ddadevops-clj-cljs:4.11.3"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
@ -17,40 +24,45 @@ services:
- .shadow-cljs/
- .m2
before_script:
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
- npm install
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
- npm install
.clj-uploadjob: &clj
image: domaindrivenarchitecture/lein
.clj-job: &clj
image: "domaindrivenarchitecture/ddadevops-clj:4.11.3"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2
before_script:
- mkdir -p /root/.lein
- echo "{:auth {:repository-auth {#\"clojars\" {:username \"${CLOJARS_USER}\" :password \"${CLOJARS_TOKEN_DOMAINDRIVENARCHITECTURE}\" }}}}" > ~/.lein/profiles.clj
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- mkdir -p /root/.lein
- echo "{:auth {:repository-auth {#\"clojars\" {:username \"${CLOJARS_USER}\" :password \"${CLOJARS_TOKEN_DOMAINDRIVENARCHITECTURE}\" }}}}" > ~/.lein/profiles.clj
test-cljs:
<<: *cljs
stage: build_and_test
script:
- shadow-cljs compile test
- node target/node-tests.js
.tag_only: &tag_only
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
test-clj:
<<: *clj
stage: build_and_test
script:
- lein test
- pyb test_clj
test-cljs:
<<: *cljs
stage: build_and_test
script:
- pyb test_cljs
test-schema:
<<: *clj
stage: build_and_test
script:
- lein uberjar
- java -jar target/uberjar/c4k-taiga-standalone.jar src/test/resources/taiga-test/valid-config.yaml src/test/resources/taiga-test/valid-auth.yaml | kubeconform --kubernetes-version 1.23.0 --strict --skip Certificate -
- pyb test_schema
artifacts:
expire_in: 1h
paths:
- target/uberjar
@ -58,10 +70,8 @@ report-frontend:
<<: *cljs
stage: package
script:
- mkdir -p target/frontend-build
- shadow-cljs run shadow.cljs.build-report frontend target/frontend-build/build-report.html
- pyb report_frontend
artifacts:
expire_in: 1h
paths:
- target/frontend-build/build-report.html
@ -69,13 +79,8 @@ package-frontend:
<<: *cljs
stage: package
script:
- mkdir -p target/frontend-build
- shadow-cljs release frontend
- cp public/js/main.js target/frontend-build/c4k-taiga.js
- sha256sum target/frontend-build/c4k-taiga.js > target/frontend-build/c4k-taiga.js.sha256
- sha512sum target/frontend-build/c4k-taiga.js > target/frontend-build/c4k-taiga.js.sha512
- pyb package_frontend
artifacts:
expire_in: 1h
paths:
- target/frontend-build
@ -83,47 +88,37 @@ package-uberjar:
<<: *clj
stage: package
script:
- lein uberjar
- sha256sum target/uberjar/c4k-taiga-standalone.jar > target/uberjar/c4k-taiga-standalone.jar.sha256
- sha512sum target/uberjar/c4k-taiga-standalone.jar > target/uberjar/c4k-taiga-standalone.jar.sha512
- pyb package_uberjar
artifacts:
expire_in: 1h
paths:
- target/uberjar
upload-clj-release:
<<: *clj
stage: upload
rules:
- if: '$CI_COMMIT_TAG != null'
script:
- lein deploy
release:
image: registry.gitlab.com/gitlab-org/release-cli:latest
stage: upload
rules:
- if: '$CI_COMMIT_TAG != null'
artifacts:
expire_in: 24h
paths:
- target/uberjar
- target/frontend-build
script:
- apk --no-cache add curl
- |
release-cli create --name "Release $CI_COMMIT_TAG" --tag-name $CI_COMMIT_TAG \
--assets-link "{\"name\":\"c4k-taiga-standalone.jar\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/c4k-taiga-standalone.jar\"}" \
--assets-link "{\"name\":\"c4k-taiga-standalone.jar.sha256\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/c4k-taiga-standalone.jar.sha256\"}" \
--assets-link "{\"name\":\"c4k-taiga-standalone.jar.sha512\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/c4k-taiga-standalone.jar.sha512\"}" \
--assets-link "{\"name\":\"c4k-taiga.js\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/c4k-taiga.js\"}" \
--assets-link "{\"name\":\"c4k-taiga.js.sha256\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/c4k-taiga.js.sha256\"}" \
--assets-link "{\"name\":\"c4k-taiga.js.sha512\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/c4k-taiga.js.sha512\"}" \
taiga-image-test-publish:
image: domaindrivenarchitecture/devops-build:latest
stage: image
rules:
- if: '$CI_COMMIT_TAG != null'
package-native:
<<: *clj
stage: package
script:
- cd infrastructure/c4k-taiga-build && pyb image test publish
- pyb package_native
artifacts:
paths:
- target/graalvm
release-to-clojars:
<<: *clj
<<: *tag_only
stage: upload
script:
- pyb upload_clj
release-to-forgejo:
<<: *clj
<<: *tag_only
stage: upload
script:
- pyb publish_artifacts
backup-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/backup && pyb image publish

162
README.md
View file

@ -2,116 +2,88 @@
[![Clojars Project](https://img.shields.io/clojars/v/org.domaindrivenarchitecture/c4k-taiga.svg)](https://clojars.org/org.domaindrivenarchitecture/c4k-taiga) [![pipeline status](https://gitlab.com/domaindrivenarchitecture/c4k-taiga/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/c4k-taiga/-/commits/main)
[<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa-gmbh.de/img/community/Mastodon_Logotype.svg" width=20 alt="team@social.meissa-gmbh.de"> team@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@team) | [taiga & Blog](https://domaindrivenarchitecture.org)
## Configuration Issues
We currently can no login even after `python manage.py createsuperuser --noinput` in the taiga-back-deployment container. What might help: https://docs.taiga.io/setup-production.html#taiga-back
Note: taiga-manage,-back und -async verwenden die gleichen docker images mit unterschiedlichen entry-points.
https://github.com/kaleidos-ventures/taiga-docker
https://community.taiga.io/t/taiga-30min-setup/170
### Steps to start and get an admin user
Philosophy: First create the superuser, then populate the DB.
https://docs.taiga.io/setup-production.html#taiga-back
https://docs.taiga.io/setup-production.html#_configure_an_admin_user
https://github.com/kaleidos-ventures/taiga-back/blob/main/docker/entrypoint.sh
In the init container we create the super user. Difference between init-container and container: CELERY_ENABLED: false
The init container gets the following command and args:
```yaml
command: ["/bin/bash"]
args: ["-c", "source /opt/venv/bin/activate && python manage.py createsuperuser --noinput"]
```
Thus the dockerfile default entrypoint is ignored.
Problem: Login using this method is still not available with the proposed credentials.
#### Option 1: Init container, currently under test
Create an init container (celery disabled) with the python manage.py command and the taiga-manage createsuperuser args
#### Option 2: Single container
Create a single container that has celery disabled at the beginning.
Runs the following cmds:
* python manage.py taiga-manage createsuperuser
* enable celery
* execute entrypoint.sh
### HTTPS
Terminiert am ingress. Wie interagiert das mit taiga?
Eventuell wird dies hier relevant:
https://github.com/kaleidos-ventures/taiga-docker#session-cookies-in-django-admin
### Docker Compose (DC) -> Kubernetes
We implemented a deployment and service in kubernetes for each DC Service.
Configmaps and secrets were implemented, to avoid redundancy and readability also to increase security a bit.
For all volumes described in DC we implemented PVCs and volume refs.
A config.py (used for taiga-back ) was introduced for reference.
A config.json (used for taiga-front ) was introduced for reference.
NB: It might be necessary to actually map both from a config map to their respective locations in taiga-back and taiga-front. Description for that is [here](https://docs.taiga.io/setup-production.html).
A mix of both env-vars and config.py in one container is not possible.
#### depends_on
We currently assume, that it will work without explicitly defining a startup order.
#### DC Networking
https://github.com/compose-spec/compose-spec/blob/master/spec.md
The `hostname` KW sets the hostname of a container.
It should have no effect on the discoverability of the container in kubernetes.
The `networks` KW defines the networks that service containers are attached to, referencing entries under the top-level networks key.
This should be taken care of by our kubernetes installation.
#### Pod to Pod Possible Communications
Taiga containers that need to reach other taiga containers:
taiga-async -> taiga-async-rabbitmq
taiga-events -> taiga-events-rabbitmq
This is not quite clear, but probably solved with the implementation of services.
### Deployments
Separate deployments exist for each of the taiga modules:
Taiga-back reads many values in config.py from env vars as can be seen in the taiga-back [config.py](
https://github.com/kaleidos-ventures/taiga-back/blob/main/docker/config.py). These are read from configmaps and secrets in the deployment.
[<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa.de/images/parts/contact/mastodon36_hue9b2464f10b18e134322af482b9c915e_5501_filter_14705073121015236177.png" width=20 alt="M"> meissa@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@meissa) | [Blog](https://domaindrivenarchitecture.org) | [Website](https://meissa.de)
## Purpose
Easily generate a config for a small scale Taiga deployment. Complete with initial super user and configurable values for flexibility.
## Status
Ready for production. We are about to migrate to taiga on our own servers.
## Try out
Click on the image to try out live in your browser:
[![Try it out](doc/tryItOut.png "Try out yourself")](https://domaindrivenarchitecture.org/pages/dda-provision/c4k-taiga/)
Your input will stay in your browser. No server interaction is required.
## Usage
You need:
To generate your configuration:
...
1. Create an auth.yaml and a config.yaml
* and a kubernetes cluster provisioned by [provs]
You can find examples in src/test/resources/taiga-test in this repository.
Please check the ```def auth?``` and ```def config?``` definitions in src/main/cljc/dda/c4k_taiga/taiga.cljc
for required and optional values. When you plan to use the dda_backup solution, you also need the keys:
...
Let c4k-taiga generate your .yaml file.
Apply this file on your cluster with `kubectl apply -f yourApp.yaml`.
- aws-access-key-id: "AWS_KEY_ID"
- aws-secret-access-key: "AWS_KEY_SECRET"
- restic-password: ""
in your auth.yaml
and
- restic-repository: "repo-path"
in your config.yaml.
2. install jarwrapper
```bash
sudo apt install jarwrapper # on debian derivatives
```
3. execute the c4k-taiga-standalone.jar
```bash
java -jar c4k-taiga-standalone.jar config.yaml auth.yaml
```
To set up you need:
* A working DNS route to the FQDN of your taiga installation
* A kubernetes cluster provisioned by [provs]
* The .yaml file generated by c4k-taiga-standalone.jar
Apply this file on your cluster with `kubectl apply -f application.yaml`.
Done.
### resource requests and limits
## Setup
You may want to adjust the resource requests and limits of the build and init containers to your specific scenario.
`python manage.py createsuperuser --noinput`
## Administration
You can access the administration of the taiga installation via: your.taiga.url/admin/
In order to login, you first have to create a superuser.
1. Connect to taiga-back pod: `kubectl exec -it taiga-back-deployment-... -- bash`
2. `source /opt/venv/bin/activate && python manage.py createsuperuser --noinput`
## Backup
You need some form of cloud storage like AWS buckets and the respective access credentials
to make the backups work correctly.
For a working backup you need to save Taiga DB & Taiga Media as described here:
https://docs.taiga.io/backup-and-restore.html
For further instruction please refer to BackupAndRestore.md in doc/ in this repository.
## Development & mirrors
@ -126,7 +98,7 @@ For more details about our repository model see: https://repo.prod.meissa.de/mei
## License
Copyright © 2022 meissa GmbH
Copyright © 2024 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License")
Pls. find licenses of our subcomponents [here](doc/SUBCOMPONENT_LICENSE)

233
build.py Normal file
View file

@ -0,0 +1,233 @@
from os import environ
from subprocess import run
from pybuilder.core import init, task
from ddadevops import *
default_task = "dev"
base_name = "taiga"
name = 'c4k-taiga'
MODULE = 'not-used'
PROJECT_ROOT_PATH = '.'
@init
def initialize(project):
project.build_depends_on("ddadevops>=4.7.0")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": [],
"mixin_types": ["RELEASE"],
"release_primary_build_file": "project.clj",
"release_secondary_build_files": [
"package.json",
"infrastructure/backup/build.py",
],
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": name,
"release_artifacts": [
f"target/graalvm/{name}",
f"target/uberjar/{name}-standalone.jar",
f"target/frontend-build/{name}.js",
],
"release_main_branch": "main",
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
def test(project):
test_clj(project)
test_cljs(project)
test_schema(project)
@task
def test_clj(project):
run("lein test", shell=True, check=True)
@task
def test_cljs(project):
run("shadow-cljs compile test", shell=True, check=True)
run("node target/node-tests.js", shell=True, check=True)
@task
def test_schema(project):
run("lein uberjar", shell=True, check=True)
run(
f"java -jar target/uberjar/{name}-standalone.jar "
+ f"src/test/resources/{base_name}-test/valid-config.yaml "
+ f"src/test/resources/{base_name}-test/valid-auth.yaml | "
+ "kubeconform --kubernetes-version 1.23.0 --strict --skip Certificate -",
shell=True,
check=True,
)
@task
def report_frontend(project):
run("mkdir -p target/frontend-build", shell=True, check=True)
run(
"shadow-cljs run shadow.cljs.build-report frontend target/frontend-build/build-report.html",
shell=True,
check=True,
)
@task
def package_frontend(project):
run("mkdir -p target/frontend-build", shell=True, check=True)
run("shadow-cljs release frontend", shell=True, check=True)
run(
f"cp public/js/main.js target/frontend-build/{name}.js",
shell=True,
check=True,
)
run(
f"sha256sum target/frontend-build/{name}.js > target/frontend-build/{name}.js.sha256",
shell=True,
check=True,
)
run(
f"sha512sum target/frontend-build/{name}.js > target/frontend-build/{name}.js.sha512",
shell=True,
check=True,
)
@task
def package_uberjar(project):
run(
f"sha256sum target/uberjar/{name}-standalone.jar > target/uberjar/{name}-standalone.jar.sha256",
shell=True,
check=True,
)
run(
f"sha512sum target/uberjar/{name}-standalone.jar > target/uberjar/{name}-standalone.jar.sha512",
shell=True,
check=True,
)
@task
def package_native(project):
run(
"mkdir -p target/graalvm",
shell=True,
check=True,
)
run(
"native-image " +
"--native-image-info " +
"--report-unsupported-elements-at-runtime " +
"--no-server " +
"--no-fallback " +
"--features=clj_easy.graal_build_time.InitClojureClasses " +
f"-jar target/uberjar/{project.name}-standalone.jar " +
"-H:IncludeResources=.*.yaml " +
"-H:Log=registerResource:verbose " +
f"-H:Name=target/graalvm/{project.name}",
shell=True,
check=True,
)
run(
f"sha256sum target/graalvm/{project.name} > target/graalvm/{project.name}.sha256",
shell=True,
check=True,
)
run(
f"sha512sum target/graalvm/{project.name} > target/graalvm/{project.name}.sha512",
shell=True,
check=True,
)
@task
def upload_clj(project):
run("lein deploy", shell=True, check=True)
@task
def inst(project):
package_uberjar(project)
package_native(project)
run(
f"sudo install -m=755 target/uberjar/{project.name}-standalone.jar /usr/local/bin/{project.name}-standalone.jar",
shell=True,
check=True,
)
run(
f"sudo install -m=755 target/graalvm/{project.name} /usr/local/bin/{project.name}",
shell=True,
check=True,
)
@task
def lint(project):
#run(
# "lein eastwood",
# shell=True,
# check=True,
#)
run(
"lein ancient check",
shell=True,
check=True,
)
@task
def patch(project):
linttest(project, "PATCH")
release(project)
@task
def minor(project):
linttest(project, "MINOR")
release(project)
@task
def major(project):
linttest(project, "MAJOR")
release(project)
@task
def dev(project):
linttest(project, "NONE")
@task
def prepare(project):
build = get_devops_build(project)
build.prepare_release()
@task
def tag(project):
build = get_devops_build(project)
build.tag_bump_and_push_release()
@task
def publish_artifacts(project):
build = get_devops_build(project)
build.publish_artifacts()
def release(project):
prepare(project)
tag(project)
def linttest(project, release_type):
build = get_devops_build(project)
build.update_release_type(release_type)
test_clj(project)
test_cljs(project)
test_schema(project)
lint(project)

47
doc/BackupAndRestore.md Normal file
View file

@ -0,0 +1,47 @@
# Backup Architecture details
![](backup.svg)
* we use restic to produce small & encrypted backups
* backup is scheduled at `schedule: "10 23 * * *"`
* Cloud stores files on `/var/jira`, these files are backuped. If you create a jira xml backup located in /var/jira this file will also be backed up.
* postgres db is backed up as pgdump
## Manual init the restic repository for the first time
1. Scale backup-restore deployment up:
`kubectl scale deployment backup-restore --replicas=1`
1. exec into pod and execute restore pod
`kubectl exec -it backup-restore -- /usr/local/bin/init.sh`
1. Scale backup-restore deployment down:
`kubectl scale deployment backup-restore --replicas=0`
## Manual backup the restic repository for the first time
1. Scale gateway and front deployment down:
`kubectl scale deployment taiga-gateway-deployment --replicas=0`
`kubectl scale deployment taiga-front-deployment --replicas=0`
1. Scale backup-restore deployment up:
`kubectl scale deployment backup-restore --replicas=1`
1. exec into pod and execute restore pod
`kubectl exec -it backup-restore -- /usr/local/bin/backup.sh`
1. Scale backup-restore deployment down:
`kubectl scale deployment backup-restore --replicas=0`
1. Scale gateway and front deployment up:
`kubectl scale deployment taiga-front-deployment --replicas=1`
`kubectl scale deployment taiga-gateway-deployment --replicas=1`
## Manual restore
1. Scale gateway and front deployment down:
`kubectl scale deployment taiga-gateway-deployment --replicas=0`
`kubectl scale deployment taiga-front-deployment --replicas=0`
2. Scale backup-restore deployment up:
`kubectl scale deployment backup-restore --replicas=1`
3. exec into pod and execute restore pod:
`kubectl exec -it backup-restore -- /usr/local/bin/restore.sh`
4. Scale backup-restore deployment down:
`kubectl scale deployment backup-restore --replicas=0`
5. Scale gateway and front deployment up:
`kubectl scale deployment taiga-front-deployment --replicas=1`
`kubectl scale deployment taiga-gateway-deployment --replicas=1`

107
doc/Development.md Normal file
View file

@ -0,0 +1,107 @@
# Project Setup
## clj setup
### install leiningen
```
sudo apt install leiningen
```
or manually using Instructions on https://leiningen.org/#install
### install vscode + extensions
```
sudo snap install code
```
or with packages from https://code.visualstudio.com/Download
install extension "Calva: Clojure & ClojureScript Interactive Programming"
## cljs / js-dev setup
```
sudo apt install npm
sudo npm install -g npx
# maybe
sudo npm install -g shadow-cljs
# in project root to retrieve all dependencies
npm install --ignore-scripts
npx shadow-cljs compile test
```
### create frontend script
```
npx shadow-cljs release frontend
```
## graalvm-setup
```
curl -LO https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz
# unpack
tar -xzf graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz
sudo mv graalvm-community-openjdk-21.0.2+13.1 /usr/lib/jvm/
sudo ln -s /usr/lib/jvm/graalvm-community-openjdk-21.0.2+13.1 /usr/lib/jvm/graalvm-21
sudo ln -s /usr/lib/jvm/graalvm-21/bin/gu /usr/local/bin
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm-21/bin/java 2
sudo update-alternatives --config java
sudo ln -s /usr/lib/jvm/graalvm-21/bin/native-image /usr/local/bin
# deps
sudo apt-get install build-essential libz-dev zlib1g-dev
# build
cd ~/repo/c4k/c4k-forgejo
lein uberjar
mkdir -p target/graalvm
lein native
# execute
./target/graalvm/c4k-cloud -h
./target/graalvm/c4k-cloud src/test/resources/valid-config.edn src/test/resources/valid-auth.edn
./target/graalvm/c4k-cloud src/test/resources/invalid-config.edn src/test/resources/invalid-auth.edn
```
## c4k-setup
### install kubectl
```
sudo -i
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" \
| tee -a /etc/apt/sources.list.d/kubernetes.list
apt update && apt install kubectl
kubectl completion bash >> /etc/bash_completion.d/kubernetes
```
### install kubeconform
```
curl -Lo /tmp/kubeconform.tar.gz https://github.com/yannh/kubeconform/releases/download/v0.4.7/kubeconform-linux-amd64.tar.gz
tar -xf /tmp/kubeconform.tar.gz
sudo cp kubeconform /usr/local/bin
```
### remote access to c4k
```
scp -r root@devops.test.meissa-gmbh.de:/home/c4k/.kube ~/
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@devops.test.meissa-gmbh.de -L 8002:localhost:8002 -L 6443:192.168.5.1:6443
# add in /etc/hosts "127.0.0.1 kubernetes"
# change in ~/.kube/config 192.168.5.1 -> kubernetes
kubectl get pods
```
### deploy cloud
```
java -jar target/uberjar/c4k-cloud-standalone.jar valid-config.edn valid-auth.edn | kubeconform --kubernetes-version 1.19.0 --strict --skip Certificate -
java -jar target/uberjar/c4k-cloud-standalone.jar valid-config.edn my-auth.edn | kubectl apply -f -
```

View file

@ -1,5 +1,7 @@
# Release process
(Deprecated, please refer to the build.py instead.)
## ... for testing (snapshots)
Make sure your clojars.org credentials are correctly set in your ~/.lein/profiles.clj file.

View file

@ -1,26 +0,0 @@
```mermaid
C4Context
title c4k-webserver
Boundary(website, "website") {
System(website_ing1, "ingress f. host meissa-gmbh.de")
System(website_ing2, "ingress f. host meissa.de")
Boundary(website_pod, "website pod"){
Boundary(aaa, "website container") {
System(ws, "webserver")
SystemDb(file_html, "static html")
Rel(ws, file_html, "file ro")
}
Boundary(aab, "cron build website") {
System(git_clone, "git clone/pull & lein ring server & copy to static html")
SystemDb(file_git, "git repo")
Rel(git_clone, file_git, "file rw")
Rel(git_clone, file_html, "file rw")
}
}
Rel(website_ing1, ws, "http")
Rel(website_ing2, ws, "http")
}
```
[![](https://mermaid.ink/img/pako:eNqNU8tugzAQ_JWVD1UqJaka5cSxSX-guSIhgxewamxkL01RxL_XQFExSdr6gtee2ccMvrDMCGQRO-wPRhN-UqzBL5KkELL9--aMqUP7gXa8eDGNFty2q_5cEq4hZt_bmD3CZUT169Q6wmrCJVIXzz3Yfy06B_kWSuMIKpTO8U1RpeVW9Dl-y7C7n2FJXjaa1EbMmgUfevys34DEOZ-DM68Nl9qLEM647NRNpFGxeT8h9JiucqkwKalSPccRJ5lBH95kvaEasgekPgBrrgjd3aHSnpZZoyFtpBJw27nFVIWkJFNGD177AIbgqW6UggdQKDVY7wmMQ_ujzNQtkIG_hgql8JmnAhZrc1eGWTtz3ijG-d-0UMRrXjfb_9zMpB0sCX7u0f6SqA6yLYC7BTAs4guwNavQVlwK_y4HW2JGJVbepshvBea8URSzWHce2tSCE74KScayKOfK4Zrxhsyp1RmLyDY4gY6SF5ZX42H3BTITMPU)](https://mermaid.live/edit#pako:eNqNU8tugzAQ_JWVD1UqJaka5cSxSX-guSIhgxewamxkL01RxL_XQFExSdr6gtee2ccMvrDMCGQRO-wPRhN-UqzBL5KkELL9--aMqUP7gXa8eDGNFty2q_5cEq4hZt_bmD3CZUT169Q6wmrCJVIXzz3Yfy06B_kWSuMIKpTO8U1RpeVW9Dl-y7C7n2FJXjaa1EbMmgUfevys34DEOZ-DM68Nl9qLEM647NRNpFGxeT8h9JiucqkwKalSPccRJ5lBH95kvaEasgekPgBrrgjd3aHSnpZZoyFtpBJw27nFVIWkJFNGD177AIbgqW6UggdQKDVY7wmMQ_ujzNQtkIG_hgql8JmnAhZrc1eGWTtz3ijG-d-0UMRrXjfb_9zMpB0sCX7u0f6SqA6yLYC7BTAs4guwNavQVlwK_y4HW2JGJVbepshvBea8URSzWHce2tSCE74KScayKOfK4Zrxhsyp1RmLyDY4gY6SF5ZX42H3BTITMPU)

View file

@ -1,632 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.22-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="154.25" width="221.25" x="674.8125" y="-212.076171875"/>
<y:Fill color="#C0C0C0" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" textColor="#000000" verticalTextPosition="bottom" visible="true" width="36.292236328125" x="4.0" xml:space="preserve" y="4.0">Client</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="514.0" width="1041.25" x="-97.5625" y="-35.638671875"/>
<y:Fill color="#3366FF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="28.515846252441406" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.1511993408203" x="4.0" xml:space="preserve" y="4.0">Hetzner-Server</y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="518.625" y="266.69427546920065">
<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.022751508694942912" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
</y:ModelParameter>
</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="426.5" width="691.75" x="-57.5" y="10.111328125"/>
<y:Fill color="#33CCCC" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" textColor="#000000" verticalTextPosition="bottom" visible="true" width="39.61585998535156" x="4.0" xml:space="preserve" y="4.0">Node</y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="85.0" width="93.75" x="290.0" y="60.548828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.34767150878906" x="3.7011642456054688" xml:space="preserve" y="30.966041564941406">Webserver 1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="octagon"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="98.75" width="78.75" x="128.9375" y="53.673828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.10578918457031" x="8.822105407714844" xml:space="preserve" y="37.841041564941406">Ingress1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="fatarrow2"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="98.75" width="78.75" x="128.9375" y="182.423828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.10578918457031" x="8.822105407714844" xml:space="preserve" y="37.841041564941406">Ingress2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="fatarrow2"/>
</y:ShapeNode>
</data>
</node>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="85.0" width="93.75" x="290.0" y="189.298828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.34767150878906" x="3.7011642456054688" xml:space="preserve" y="30.966041564941406">Webserver 2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="octagon"/>
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="112.5" width="123.75" x="-47.5" y="175.548828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.3358154296875" x="35.20709228515625" xml:space="preserve" y="44.716041564941406">Metallb<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="octagon"/>
</y:ShapeNode>
</data>
</node>
<node id="n8">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="153.75" width="148.75" x="-1048.8125" y="-1.1875"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="28.515846252441406" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="84.38749694824219" x="32.181251525878906" xml:space="preserve" y="62.6170768737793">pybuilder<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n9">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="160.0" width="148.75" x="-680.0625" y="266.25"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="28.515846252441406" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.15971374511719" x="48.795143127441406" xml:space="preserve" y="65.7420768737793">provs<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n10">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="160.0" width="148.75" x="-1048.8125" y="266.25"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="28.515846252441406" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.547821044921875" x="58.10108947753906" xml:space="preserve" y="65.7420768737793">c4k<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n11">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="153.75" width="148.75" x="-680.0625" y="-1.1875"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="18" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="28.515846252441406" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.96148681640625" x="30.894256591796875" xml:space="preserve" y="62.6170768737793">terraform<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n12">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="75.0" width="75.0" x="-643.1875" y="-173.138671875"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="25.792043685913086" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.8880615234375" x="18.55596923828125" xml:space="preserve" y="24.603978157043457">AWS<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n13">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="75.0" width="75.0" x="-331.90625" y="38.1875"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="16" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="25.792043685913086" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.69612121582031" x="5.651939392089844" xml:space="preserve" y="24.603978157043457">Hetzner<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n14">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="75.0" width="141.25" x="714.8125" y="-172.451171875"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="61.432403564453125" x="39.90879821777344" xml:space="preserve" y="27.327942848205566">local build<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n15">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="63.75" width="118.75" x="456.5" y="71.173828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="47.52583312988281" x="35.612083435058594" xml:space="preserve" y="20.341041564941406">Mount<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.3167724609375" x="4.0" xml:space="preserve" y="39.40588569641113">Holds website data
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="0.5" nodeRatioX="-0.5" nodeRatioY="0.5" offsetX="4.0" offsetY="-4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n16">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="63.75" width="118.75" x="456.5" y="199.923828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="47.52583312988281" x="35.612083435058594" xml:space="preserve" y="20.341041564941406">Mount<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.3167724609375" x="4.0" xml:space="preserve" y="39.40588569641113">Holds website data
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="0.5" nodeRatioX="-0.5" nodeRatioY="0.5" offsetX="4.0" offsetY="-4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n17">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="98.75" width="78.75" x="128.9375" y="311.173828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="15.255950927734375" x="31.747024536132812" xml:space="preserve" y="37.841041564941406">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="fatarrow2"/>
</y:ShapeNode>
</data>
</node>
<node id="n18">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="85.0" width="93.75" x="290.0" y="318.048828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="15.255950927734375" x="39.24702453613281" xml:space="preserve" y="30.966041564941406">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="octagon"/>
</y:ShapeNode>
</data>
</node>
<node id="n19">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="63.75" width="118.75" x="456.5" y="328.673828125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="15.255950927734375" x="51.74702453613281" xml:space="preserve" y="20.341041564941406">...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="111.3167724609375" x="4.0" xml:space="preserve" y="39.40588569641113">Holds website data
<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="-0.5" labelRatioY="0.5" nodeRatioX="-0.5" nodeRatioY="0.5" offsetX="4.0" offsetY="-4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n20">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="74.375" width="255.5" x="657.6875" y="65.861328125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="250.9450225830078" x="2.2774887084960938" xml:space="preserve" y="25.653541564941406">Folder in /var/www/fqdn-folder-name<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n21">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="74.375" width="255.5" x="657.6875" y="194.611328125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="250.9450225830078" x="2.2774887084960938" xml:space="preserve" y="25.653541564941406">Folder in /var/www/fqdn-folder-name<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n22">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="74.375" width="255.5" x="657.6875" y="323.361328125"/>
<y:Fill color="#FFCC00" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="14" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="23.067916870117188" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="250.9450225830078" x="2.2774887084960938" xml:space="preserve" y="25.653541564941406">Folder in /var/www/fqdn-folder-name<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="roundrectangle"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n7" target="n7">
<data key="d10">
<y:ArcEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="14.375" y="231.798828125"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:Arc height="0.0" ratio="1.0" type="fixedRatio"/>
</y:ArcEdge>
</data>
</edge>
<edge id="e1" source="n7" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n7" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n4" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n8" target="n11">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="85.26309204101562" x="67.36845397949219" xml:space="preserve" y="10.293983459472656">out_conf.edn
out_auth.edn<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n11" target="n9">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="40.27003479003906" x="8.364982604980469" xml:space="preserve" y="31.418983459472656">IP
FQDN<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n11" target="n12">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-625.0275" y="-58.35633333333334"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.706016540527344" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.841064453125" x="-76.92666845703047" xml:space="preserve" y="-69.5675916035971">DNS Entry<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n11" target="n13">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-441.69499999999994" y="98.875"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="68.63603210449219" x="65.34923394775524" xml:space="preserve" y="18.702248697916332">ssh keys
server size<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n13" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="57.11804962158203" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="108.4290771484375" x="11.446461425783014" xml:space="preserve" y="19.35144684855112">spin up server
of requested size
with ssh keys<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n9" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e10" source="n9" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e11" source="n9" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="95.46807861328125" x="124.88346069335978" xml:space="preserve" y="-5.053327657063903">k3s and
c4k application<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e12" source="n9" target="n10">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-787.3125" y="291.125"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="85.26309204101562" x="-102.75654602050781" xml:space="preserve" y="-76.6949691772461">out_conf.edn
out_auth.edn<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e13" source="n10" target="n9">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-793.8125" y="404.125"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.706016540527344" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="132.40110778808594" x="18.766946105956777" xml:space="preserve" y="44.647786814371784">application yaml files<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e14" source="n4" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e15" source="n5" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e16" source="n6" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e17" source="n17" target="n18">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e18" source="n15" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e19" source="n20" target="n15">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e20" source="n16" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e21" source="n19" target="n18">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e22" source="n4" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e23" source="n2" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e24" source="n7" target="n17">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e25" source="n6" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e26" source="n22" target="n19">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e27" source="n21" target="n16">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e28" source="n14" target="n20">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.39651489257812" x="4.9553675537108575" xml:space="preserve" y="75.47775874328613">copy to
via scp/rsync<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e29" source="n13" target="n11">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-438.44499999999994" y="62.251666666666665"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="39.41203308105469" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="30.31201171875" x="-116.9447558593746" xml:space="preserve" y="-49.27187929280607">IPv4
IPv6<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e30" source="n12" target="n11">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="-588.3494999999999" y="-62.498333333333335"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="left" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="13" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="21.706016540527344" horizontalTextPosition="center" iconTextGap="4" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.99903869628906" x="13.953326354981414" xml:space="preserve" y="25.047580271402865">ssh keys<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -0,0 +1,49 @@
from os import environ
from pybuilder.core import task, init
from ddadevops import *
name = "c4k-taiga-backup"
MODULE = "docker"
PROJECT_ROOT_PATH = "../.."
@init
def initialize(project):
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
}
project.build_depends_on("ddadevops>=4.0.0-dev")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
def image(project):
build = get_devops_build(project)
build.image()
@task
def drun(project):
build = get_devops_build(project)
build.drun()
@task
def publish(project):
build = get_devops_build(project)
build.dockerhub_login()
build.dockerhub_publish()
@task
def test(project):
build = get_devops_build(project)
build.test()

View file

@ -0,0 +1,5 @@
FROM domaindrivenarchitecture/dda-backup:latest
# Prepare Entrypoint Script
ADD resources /tmp
RUN /tmp/install.sh

View file

@ -0,0 +1,17 @@
#!/bin/bash
function main() {
file_env AWS_ACCESS_KEY_ID
file_env AWS_SECRET_ACCESS_KEY
file_env RESTIC_DAYS_TO_KEEP 30
file_env RESTIC_MONTHS_TO_KEEP 12
backup-db-dump
backup-directory "/media"
}
source /usr/local/lib/functions.sh
source /usr/local/lib/pg-functions.sh
source /usr/local/lib/file-functions.sh
main

View file

@ -0,0 +1,13 @@
#!/bin/bash
function main() {
create-pg-pass
while true; do
sleep 1m
done
}
source /usr/local/lib/functions.sh
source /usr/local/lib/pg-functions.sh
main

View file

@ -0,0 +1,11 @@
#!/bin/bash
function main() {
create-pg-pass
/usr/local/bin/backup.sh
}
source /usr/local/lib/functions.sh
source /usr/local/lib/pg-functions.sh
main

View file

@ -0,0 +1,14 @@
#!/bin/bash
function main() {
file_env AWS_ACCESS_KEY_ID
file_env AWS_SECRET_ACCESS_KEY
init-database-repo
init-file-repo
}
source /usr/local/lib/functions.sh
source /usr/local/lib/pg-functions.sh
source /usr/local/lib/file-functions.sh
main

View file

@ -0,0 +1,21 @@
#!/bin/bash
set -exo pipefail
function main()
{
{
install -m 0700 /tmp/entrypoint.sh /
install -m 0700 /tmp/entrypoint-start-and-wait.sh /
install -m 0700 /tmp/init.sh /usr/local/bin/
install -m 0700 /tmp/backup.sh /usr/local/bin/
install -m 0700 /tmp/restore.sh /usr/local/bin/
install -m 0700 /tmp/restic-snapshots.sh /usr/local/bin/
cleanupDocker
} > /dev/null
}
source /tmp/install_functions_debian.sh
DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes main

View file

@ -0,0 +1,14 @@
#!/bin/bash
function main() {
file_env AWS_ACCESS_KEY_ID
file_env AWS_SECRET_ACCESS_KEY
restic -r ${RESTIC_REPOSITORY}/files snapshots
restic -r ${RESTIC_REPOSITORY}/pg-database snapshots
}
source /usr/local/lib/functions.sh
source /usr/local/lib/file-functions.sh
main

View file

@ -0,0 +1,29 @@
#!/bin/bash
function main() {
file_env AWS_ACCESS_KEY_ID
file_env AWS_SECRET_ACCESS_KEY
file_env POSTGRES_DB
file_env POSTGRES_PASSWORD
file_env POSTGRES_USER
# Restore latest snapshot into /var/backups/restore
restore-directory '/var/backups/restore'
mv /var/backups/restore/* /media
# adjust file permissions for the taiga user
chown -R 999:999 /media
# Restore db
drop-create-db
restore-db
}
source /usr/local/lib/functions.sh
source /usr/local/lib/pg-functions.sh
source /usr/local/lib/file-functions.sh
main

View file

@ -1,51 +0,0 @@
from os import environ
from pybuilder.core import task, init
from ddadevops import *
import logging
name = 'c4k-website-build'
MODULE = 'docker'
PROJECT_ROOT_PATH = '../..'
class MyBuild(DevopsDockerBuild):
pass
@init
def initialize(project):
project.build_depends_on('ddadevops>=0.12.4')
stage = 'prod'
dockerhub_user = environ.get('DOCKERHUB_USER')
if not dockerhub_user:
dockerhub_user = gopass_field_from_path('meissa/web/docker.com', 'login')
dockerhub_password = environ.get('DOCKERHUB_PASSWORD')
if not dockerhub_password:
dockerhub_password = gopass_password_from_path('meissa/web/docker.com')
tag = environ.get('CI_COMMIT_TAG')
if not tag:
tag = get_tag_from_latest_commit()
config = create_devops_docker_build_config(
stage, PROJECT_ROOT_PATH, MODULE, dockerhub_user, dockerhub_password, docker_publish_tag=tag)
build = MyBuild(project, config)
build.initialize_build_dir()
@task
def image(project):
build = get_devops_build(project)
build.image()
@task
def drun(project):
build = get_devops_build(project)
build.drun()
@task
def publish(project):
build = get_devops_build(project)
build.dockerhub_login()
build.dockerhub_publish()
@task
def test(project):
build = get_devops_build(project)
build.test()

View file

@ -1,11 +0,0 @@
FROM clojure:lein
# Prepare Entrypoint Script
ADD resources /tmp
ENV BUILDDIR="/etc/website"
ENV SOURCEDIR="/etc/websitesource"
ENV WEBSITEROOT="/var/www/html/website/"
ENV HASHFILEDIR="/var/hashfile.d"
RUN /tmp/install.sh

View file

@ -1,36 +0,0 @@
#!/bin/bash
mkdir $BUILDDIR
mkdir $SOURCEDIR
set -euo pipefail
source /usr/local/bin/functions.sh
filename="website.zip"
hashfilename="hashfile"
echo "Check for new content"
touch $HASHFILEDIR/$hashfilename
currentHash=$( cat $HASHFILEDIR/$hashfilename )
newHash=$( get-hash-data )
if [[ $currentHash == $newHash ]]
then
echo "Nothing to do"
else
echo $currentHash > $HASHFILEDIR/$hashfilename
echo "Downloading website data"
get-website-data $filename
unzip-website-data $filename
echo "Executing Custom Scripts, if applicable"
execute-scripts-when-existing
echo "Building website"
build-website
echo "Moving files"
move-website-files-to-target
fi

View file

@ -1,2 +0,0 @@
.git
.gitignore

View file

@ -1,45 +0,0 @@
#!/bin/bash
function get-website-data() {
curl -H "Authorization: token $AUTHTOKEN" -o $SOURCEDIR/$1 $GITREPOURL
}
function get-hash-data() {
curl -s -H "Authorization: token $AUTHTOKEN" $GITCOMMITURL | jq '.sha'
}
function write-hash-data() {
echo $1 > $HASHFILEDIR/$2
}
function unzip-website-data() {
unzip $SOURCEDIR/$1 -d $BUILDDIR
}
function execute-scripts-when-existing() {
websitedir=$(ls $BUILDDIR)
if [[ -f $BUILDDIR/$websitedir/$SCRIPTFILE ]]
then
checksum="$(sha256sum $BUILDDIR/$websitedir/$SCRIPTFILE | grep -oE "^[a-z0-9]+")"
if [[ "$SHA256SUM" == "$checksum" ]]
then
chmod +x $BUILDDIR/$websitedir/$SCRIPTFILE
(cd $BUILDDIR; dir=$(ls); cd $dir; ./$SCRIPTFILE) #make sure paths defined in scriptfile are relative to $dir
else
printf "Provided SHA256 Sum does not match calculated sum. Exiting."
printf "Calculated SHA256: $checksum"
printf "Given SHA256: $SHA256SUM"
exit 1
fi
else
printf "No script file provided."
fi
}
function build-website() {
(cd $BUILDDIR; dir=$(ls); cd $dir; lein run;)
}
function move-website-files-to-target() {
(cd $BUILDDIR; dir=$(ls); cd $dir; rsync -ru --exclude-from "/etc/exclude.pattern" --delete resources/public/* $WEBSITEROOT;)
}

View file

@ -1,14 +0,0 @@
#!/bin/bash
apt update > /dev/null;
apt install -y unzip rsync jq imagemagick
mkdir /etc/lein/
install -m 0700 /tmp/entrypoint.sh /
install -m 0700 /tmp/functions.sh /usr/local/bin/
install -m 0700 /tmp/exclude.pattern /etc/
install -m 0700 /tmp/project.clj /etc/lein/
cd /etc/lein;
lein deps;

View file

@ -1,11 +0,0 @@
(defproject org.domaindrivenarchitecture/c4k-website-build "0.1.1-SNAPSHOT"
:description "website c4k-build package"
:url "https://domaindrivenarchitecture.org"
:license {:name "Apache License, Version 2.0"
:url "https://www.apache.org/licenses/LICENSE-2.0.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[dda/cryogen-bootstrap "0.1.5"]]
:plugins [[lein-ring "0.12.5"]]
:main cryogen.core
:ring {:init cryogen.server/init
:handler cryogen.server/handler})

View file

@ -1,11 +0,0 @@
FROM c4k-website-build
RUN apt update
RUN apt -yqq --no-install-recommends --yes install curl default-jre-headless
RUN curl -L -o /tmp/serverspec.jar \
https://github.com/DomainDrivenArchitecture/dda-serverspec-crate/releases/download/2.0.0/dda-serverspec-standalone.jar
COPY serverspec.edn /tmp/serverspec.edn
RUN java -jar /tmp/serverspec.jar /tmp/serverspec.edn -v

View file

@ -1,4 +0,0 @@
{:file [{:path "/entrypoint.sh" :mod "700"}
{:path "/usr/local/bin/functions.sh" :mod "700"}
{:path "/etc/exclude.pattern" :mod "700"}
{:path "/etc/lein/project.clj" :mod "700"}]}

View file

@ -2,7 +2,7 @@
"name": "c4k-taiga",
"description": "Generate c4k yaml for a taiga project management deployment.",
"author": "meissa GmbH",
"version": "0.0.1-SNAPSHOT",
"version": "1.1.3-SNAPSHOT",
"homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-taiga#readme",
"repository": "https://www.npmjs.com/package/c4k-taiga",
"license": "APACHE2",
@ -23,7 +23,7 @@
"url": "https://gitlab.com/domaindrivenarchitecture/c4k-taiga/issues"
},
"dependencies": {
"js-base64": "^3.7.2",
"js-base64": "^3.7.5",
"js-yaml": "^4.0.0"
},
"devDependencies": {

View file

@ -1,11 +1,11 @@
(defproject org.domaindrivenarchitecture/c4k-taiga "0.0.1-Snapshot"
(defproject org.domaindrivenarchitecture/c4k-taiga "1.1.3-SNAPSHOT"
:description "taiga c4k-installation package"
:url "https://domaindrivenarchitecture.org"
:license {:name "Apache License, Version 2.0"
:url "https://www.apache.org/licenses/LICENSE-2.0.html"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/tools.reader "1.3.6"]
[org.domaindrivenarchitecture/c4k-common-clj "6.0.1"]
[org.clojure/tools.reader "1.4.0"]
[org.domaindrivenarchitecture/c4k-common-clj "6.1.3"]
[hickory "0.7.1" :exclusions [viebel/codox-klipse-theme]]]
:target-path "target/%s/"
:source-paths ["src/main/cljc"
@ -22,25 +22,14 @@
:uberjar {:aot :all
:main dda.c4k-taiga.uberjar
:uberjar-name "c4k-taiga-standalone.jar"
:dependencies [[org.clojure/tools.cli "1.0.214"]
[ch.qos.logback/logback-classic "1.4.5"
:dependencies [[org.clojure/tools.cli "1.1.230"]
[ch.qos.logback/logback-classic "1.5.0"
:exclusions [com.sun.mail/javax.mail]]
[org.slf4j/jcl-over-slf4j "2.0.6"]]}}
[org.slf4j/jcl-over-slf4j "2.0.12"]
[com.github.clj-easy/graal-build-time "1.0.5"]]}}
:release-tasks [["test"]
["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"]
["vcs" "tag" "v" "--no-sign"]
["change" "version" "leiningen.release/bump-version"]]
:aliases {"native" ["shell"
"native-image"
"--report-unsupported-elements-at-runtime"
"--initialize-at-build-time"
"-jar" "target/uberjar/c4k-taiga-standalone.jar"
"-H:ResourceConfigurationFiles=graalvm-resource-config.json"
"-H:Log=registerResource"
"-H:Name=target/graalvm/${:name}"]
"inst" ["shell"
"sh"
"-c"
"lein uberjar && sudo install -m=755 target/uberjar/c4k-taiga-standalone.jar /usr/local/bin/c4k-taiga-standalone.jar"]})
["change" "version" "leiningen.release/bump-version"]])

View file

@ -4,7 +4,7 @@
"src/test/cljc"
"src/test/cljs"
"src/test/resources"]
:dependencies [[org.domaindrivenarchitecture/c4k-common-cljs "6.0.1"]
:dependencies [[org.domaindrivenarchitecture/c4k-common-cljs "6.1.3"]
[hickory "0.7.1"]]
:builds {:frontend {:target :browser
:modules {:main {:init-fn dda.c4k-taiga.browser/init}}

View file

@ -0,0 +1,45 @@
(ns dda.c4k-taiga.backup
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.base64 :as b64]
[dda.c4k-common.common :as cm]
[dda.c4k-common.predicate :as pd]))
(s/def ::aws-access-key-id pd/bash-env-string?)
(s/def ::aws-secret-access-key pd/bash-env-string?)
(s/def ::restic-password pd/bash-env-string?)
(s/def ::restic-repository pd/bash-env-string?)
#?(:cljs
(defmethod yaml/load-resource :backup [resource-name]
(case resource-name
"backup/config.yaml" (rc/inline "backup/config.yaml")
"backup/cron.yaml" (rc/inline "backup/cron.yaml")
"backup/secret.yaml" (rc/inline "backup/secret.yaml")
"backup/backup-restore-deployment.yaml" (rc/inline "backup/backup-restore-deployment.yaml")
(throw (js/Error. "Undefined Resource!")))))
(defn generate-config [my-conf]
(let [{:keys [restic-repository]} my-conf]
(->
(yaml/load-as-edn "backup/config.yaml")
(cm/replace-key-value :restic-repository restic-repository))))
(defn generate-cron []
(yaml/load-as-edn "backup/cron.yaml"))
(defn generate-backup-restore-deployment [my-conf]
(let [backup-restore-yaml (yaml/load-as-edn "backup/backup-restore-deployment.yaml")]
(if (and (contains? my-conf :local-integration-test) (= true (:local-integration-test my-conf)))
(cm/replace-named-value backup-restore-yaml "CERTIFICATE_FILE" "/var/run/secrets/localstack-secrets/ca.crt")
backup-restore-yaml)))
(defn generate-secret [my-auth]
(let [{:keys [aws-access-key-id aws-secret-access-key restic-password]} my-auth]
(->
(yaml/load-as-edn "backup/secret.yaml")
(cm/replace-key-value :aws-access-key-id (b64/encode aws-access-key-id))
(cm/replace-key-value :aws-secret-access-key (b64/encode aws-secret-access-key))
(cm/replace-key-value :restic-password (b64/encode restic-password)))))

View file

@ -8,6 +8,7 @@
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.monitoring :as mon]
[dda.c4k-taiga.taiga :as taiga]
[dda.c4k-taiga.backup :as backup]
[dda.c4k-common.postgres :as postgres]))
(def default-storage-class :local-path)
@ -55,5 +56,10 @@
(taiga/generate-rabbitmq-pvc-events config)
(taiga/generate-secret auth)]
(taiga/generate-ingress-and-cert config)
(when (contains? config :restic-repository)
[(backup/generate-config config)
(backup/generate-secret auth)
(backup/generate-cron)
(backup/generate-backup-restore-deployment config)])
(when (:contains? config :mon-cfg)
(mon/generate (:mon-cfg config) (:mon-auth auth))))))))

View file

@ -13,7 +13,8 @@
[dda.c4k-common.monitoring :as mon]
[dda.c4k-common.postgres :as postgres]
[dda.c4k-common.ingress :as ing]
[clojure.string :as str]))
[clojure.string :as str]
#?(:cljs [dda.c4k-common.macros :refer-macros [inline-resources]])))
(def config-defaults {:issuer "staging"
@ -43,10 +44,10 @@
(s/def ::public-register-enabled string?) ;; ToDo maybe check for boolean string
(s/def ::enable-telemetry string?)
(s/def ::storage-class-name string?)
(s/def ::storage-media-size int?)
(s/def ::storage-static-size int?)
(s/def ::storage-async-rabbitmq-size int?)
(s/def ::storage-events-rabbitmq-size int?)
(s/def ::storage-media-size pos?)
(s/def ::storage-static-size pos?)
(s/def ::storage-async-rabbitmq-size pos?)
(s/def ::storage-events-rabbitmq-size pos?)
(def auth? (s/keys :req-un [::postgres/postgres-db-user
::postgres/postgres-db-password
@ -68,38 +69,14 @@
::storage-static-size
::storage-async-rabbitmq-size
::storage-events-rabbitmq-size
::pv-storage-size-gb
::postgres/pv-storage-size-gb
::public-register-enabled
::enable-telemetry
::mon-cfg]))
#?(:cljs
(defmethod yaml/load-resource :taiga [resource-name]
(case resource-name
"taiga/events-rabbitmq-deployment.yaml" (rc/inline "taiga/events-rabbitmq-deployment.yaml")
"taiga/gateway-deployment.yaml" (rc/inline "taiga/gateway-deployment.yaml")
"taiga/protected-deployment.yaml" (rc/inline "taiga/protected-deployment.yaml")
"taiga/gateway-configmap.yaml" (rc/inline "taiga/gateway-configmap.yaml")
"taiga/configmap.yaml" (rc/inline "taiga/configmap.yaml")
"taiga/async-service.yaml" (rc/inline "taiga/async-service.yaml")
"taiga/events-deployment.yaml" (rc/inline "taiga/events-deployment.yaml")
"taiga/async-deployment.yaml" (rc/inline "taiga/async-deployment.yaml")
"taiga/back-deployment.yaml" (rc/inline "taiga/back-deployment.yaml")
"taiga/front-deployment.yaml" (rc/inline "taiga/front-deployment.yaml")
"taiga/front-service.yaml" (rc/inline "taiga/front-service.yaml")
"taiga/gateway-service.yaml" (rc/inline "taiga/gateway-service.yaml")
"taiga/pvc-taiga-media-data.yaml" (rc/inline "taiga/pvc-taiga-media-data.yaml")
"taiga/pvc-taiga-static-data.yaml" (rc/inline "taiga/pvc-taiga-static-data.yaml")
"taiga/async-rabbitmq-deployment.yaml" (rc/inline "taiga/async-rabbitmq-deployment.yaml")
"taiga/protected-service.yaml" (rc/inline "taiga/protected-service.yaml")
"taiga/secret.yaml" (rc/inline "taiga/secret.yaml")
"taiga/async-rabbitmq-service.yaml" (rc/inline "taiga/async-rabbitmq-service.yaml")
"taiga/events-service.yaml" (rc/inline "taiga/events-service.yaml")
"taiga/back-service.yaml" (rc/inline "taiga/back-service.yaml")
"taiga/events-rabbitmq-service.yaml" (rc/inline "taiga/events-rabbitmq-service.yaml")
"taiga/rabbitmq-pvc-async.yaml" (rc/inline "taiga/rabbitmq-pvc-async.yaml")
"taiga/rabbitmq-pvc-events.yaml" (rc/inline "taiga/rabbitmq-pvc-events.yaml")
(throw (js/Error. "Undefined Resource!")))))
(get (inline-resources "taiga") resource-name)))
(defn-spec generate-ingress-and-cert cp/map-or-seq?
[config config?]
@ -112,60 +89,60 @@
config))))
(defn-spec generate-async-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/async-deployment.yaml")))
(yaml/load-as-edn "taiga/async-deployment.yaml"))
(defn-spec generate-async-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/async-service.yaml")))
(yaml/load-as-edn "taiga/async-service.yaml"))
(defn-spec generate-async-rabbitmq-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/async-rabbitmq-deployment.yaml")))
(yaml/load-as-edn "taiga/async-rabbitmq-deployment.yaml"))
(defn-spec generate-events-rabbitmq-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/events-rabbitmq-service.yaml")))
(yaml/load-as-edn "taiga/events-rabbitmq-service.yaml"))
(defn-spec generate-async-rabbitmq-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/async-rabbitmq-service.yaml")))
(yaml/load-as-edn "taiga/async-rabbitmq-service.yaml"))
(defn-spec generate-back-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/back-deployment.yaml")))
(yaml/load-as-edn "taiga/back-deployment.yaml"))
(defn-spec generate-back-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/back-service.yaml")))
(yaml/load-as-edn "taiga/back-service.yaml"))
(defn-spec generate-events-rabbitmq-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/events-rabbitmq-deployment.yaml")))
(yaml/load-as-edn "taiga/events-rabbitmq-deployment.yaml"))
(defn-spec generate-events-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/events-deployment.yaml")))
(yaml/load-as-edn "taiga/events-deployment.yaml"))
(defn-spec generate-events-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/events-service.yaml")))
(yaml/load-as-edn "taiga/events-service.yaml"))
(defn-spec generate-front-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/front-deployment.yaml")))
(yaml/load-as-edn "taiga/front-deployment.yaml"))
(defn-spec generate-front-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/front-service.yaml")))
(yaml/load-as-edn "taiga/front-service.yaml"))
(defn-spec generate-gateway-configmap cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/gateway-configmap.yaml")))
(yaml/load-as-edn "taiga/gateway-configmap.yaml"))
(defn-spec generate-gateway-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/gateway-deployment.yaml")))
(yaml/load-as-edn "taiga/gateway-deployment.yaml"))
(defn-spec generate-gateway-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/gateway-service.yaml")))
(yaml/load-as-edn "taiga/gateway-service.yaml"))
(defn-spec generate-protected-deployment cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/protected-deployment.yaml")))
(yaml/load-as-edn "taiga/protected-deployment.yaml"))
(defn-spec generate-protected-service cp/map-or-seq? []
(yaml/from-string (yaml/load-resource "taiga/protected-service.yaml")))
(yaml/load-as-edn "taiga/protected-service.yaml"))
(defn-spec generate-configmap cp/map-or-seq?
[config config?]
(let [{:keys [fqdn enable-telemetry public-register-enabled]} (merge config-defaults config)]
(-> (yaml/load-as-edn "taiga/configmap.yaml")
(-> (yaml/load-as-edn "taiga/configmap.yaml")
(cm/replace-key-value :TAIGA_SITES_DOMAIN fqdn)
(cm/replace-key-value :TAIGA_URL (str "https://" fqdn))
(cm/replace-key-value :TAIGA_WEBSOCKETS_URL (str "wss://" fqdn))
@ -176,7 +153,7 @@
[config config?]
(let [{:keys [storage-class-name storage-media-size]} (merge config-defaults config)]
(->
(yaml/from-string (yaml/load-resource "taiga/pvc-taiga-media-data.yaml"))
(yaml/load-as-edn "taiga/pvc-taiga-media-data.yaml")
(assoc-in [:spec :storageClassName] storage-class-name)
(assoc-in [:spec :resources :requests :storage] (str storage-media-size "Gi")))))
@ -184,7 +161,7 @@
[config config?]
(let [{:keys [storage-class-name storage-static-size]} (merge config-defaults config)]
(->
(yaml/from-string (yaml/load-resource "taiga/pvc-taiga-static-data.yaml"))
(yaml/load-as-edn "taiga/pvc-taiga-static-data.yaml")
(assoc-in [:spec :storageClassName] storage-class-name)
(assoc-in [:spec :resources :requests :storage] (str storage-static-size "Gi")))))
@ -195,7 +172,7 @@
rabbitmq-user rabbitmq-pw rabbitmq-erlang-cookie
django-superuser-username django-superuser-password django-superuser-email]} auth]
(->
(yaml/from-string (yaml/load-resource "taiga/secret.yaml"))
(yaml/load-as-edn "taiga/secret.yaml")
(cm/replace-key-value :TAIGA_SECRET_KEY (b64/encode taiga-secret-key))
(cm/replace-key-value :EMAIL_HOST_USER (b64/encode mailer-user))
(cm/replace-key-value :EMAIL_HOST_PASSWORD (b64/encode mailer-pw))
@ -210,7 +187,7 @@
[config config?]
(let [{:keys [storage-class-name storage-async-rabbitmq-size]} (merge config-defaults config)]
(->
(yaml/from-string (yaml/load-resource "taiga/rabbitmq-pvc-async.yaml"))
(yaml/load-as-edn "taiga/rabbitmq-pvc-async.yaml")
(assoc-in [:spec :storageClassName] storage-class-name)
(assoc-in [:spec :resources :requests :storage] (str storage-async-rabbitmq-size "Gi")))))
@ -218,7 +195,7 @@
[config config?]
(let [{:keys [storage-class-name storage-events-rabbitmq-size]} (merge config-defaults config)]
(->
(yaml/from-string (yaml/load-resource "taiga/rabbitmq-pvc-events.yaml"))
(yaml/load-as-edn "taiga/rabbitmq-pvc-events.yaml")
(assoc-in [:spec :storageClassName] storage-class-name)
(assoc-in [:spec :resources :requests :storage] (str storage-events-rabbitmq-size "Gi")))))

View file

@ -4,10 +4,11 @@
[clojure.tools.reader.edn :as edn]
[dda.c4k-common.monitoring :as mon]
[dda.c4k-taiga.core :as core]
[dda.c4k-taiga.taiga :as taiga]
[dda.c4k-common.common :as cm]
[dda.c4k-taiga.taiga :as taiga]
[dda.c4k-common.common :as cm]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.browser :as br]
))
[dda.c4k-common.postgres :as postgres]))
(defn generate-content []
(cm/concat-vec
@ -17,46 +18,38 @@
(br/generate-group
"domain"
(cm/concat-vec
(br/generate-input-field "fqdn" "The fully qualified domain name of your Taiga Instance:" "taiga.example.com")
(br/generate-input-field "issuer" "(Optional) Your issuer prod/staging:" "staging")
(br/generate-input-field "mon-cluster-name" "(Optional) monitoring cluster name:" "taiga")
(br/generate-input-field "mon-cluster-stage" "(Optional) monitoring cluster stage:" "test")
(br/generate-input-field "mon-cloud-url" "(Optional) grafana cloud url:" "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push")))
(br/generate-group
"taiga-data"
(br/generate-text-area
"taigas" "Contains fqdns, repo infos, an optional sha256sum-output for script execution for each taiga:"
"{ :taigas
[{:unique-name \"test.io\",
:fqdns [\"test.de\" \"www.test.de\"],
:gitea-host \"githost.de\",
:gitea-repo \"repo\",
:branchname \"main\",
:sha256sum-output \"123456789ab123cd345de script-file-name.sh\"}
{:unique-name \"example.io \",
:fqdns [\"example.org\" \"www.example.org\"],
:gitea-host \"githost.org\",
:gitea-repo \"repo\",
:branchname \"main\",
:build-cpu-request \"1500m\",
:build-cpu-limit \"3000m\",
:build-memory-request \"512Mi\",
:build-memory-limit \"1024Mi\"}] }"
"16"))
"options"
(cm/concat-vec
(br/generate-input-field "public-register-enabled" "(Optional) Allow public registration?" "false")
(br/generate-input-field "enable-telemetry" "(Optional) Allow anonymous collection of usage data?" "false")
(br/generate-input-field "pv-storage-size-gb" "(Optional) The volume size of your postgres DB:" "5")
(br/generate-input-field "storage-class-name" "(Optional) Name of storage class:" "local-path")
(br/generate-input-field "storage-media-size" "(Optional) The size of your media storage:" "5")
(br/generate-input-field "storage-static-size" "(Optional) The size of your static data storage:" "5")
(br/generate-input-field "storage-async-rabbitmq-size" "(Optional) The size of your rabbitmq async storage:" "5")
(br/generate-input-field "storage-events-rabbitmq-size" "(Optional) The size of your rabbitmq events storage:" "5")))
(br/generate-group
"credentials"
(br/generate-text-area
"auth" "Your authentication data for each taiga or git repo:"
"{:mon-auth
{:grafana-cloud-user \"your-user-id\"
:grafana-cloud-password \"your-cloud-password\"}
:auth
[{:unique-name \"test.io\",
:username \"someuser\",
:authtoken \"abedjgbasdodj\"}
{:unique-name \"example.io\",
:username \"someuser\",
:authtoken \"abedjgbasdodj\"}]}"
"7"))
(cm/concat-vec
(br/generate-input-field "postgres-db-user" "Your postgres user:" "postgres")
(br/generate-input-field "postgres-db-password" "Your postgres password:" "change-me")
(br/generate-input-field "mailer-user" "Allow taiga access to a mail account:" "mail[at]example.com")
(br/generate-input-field "mailer-pw" "Allow taiga access to a mail account:" "change-me")
(br/generate-input-field "django-superuser-username" "The superusers username:" "admin")
(br/generate-input-field "django-superuser-password" "The superusers password:" "change-me")
(br/generate-input-field "django-superuser-email" "The superusers email:" "mail[at]example.com")
(br/generate-input-field "rabbitmq-user" "User for rabbitmq:" "user")
(br/generate-input-field "rabbitmq-pw" "Password for the rabbitmq user:" "change-me")
(br/generate-input-field "rabbitmq-erlang-cookie" "Random hash shared among all rabbitmq pods:" "change-me")
(br/generate-input-field "taiga-secret-key" "Random key shared among all taiga pods:" "change-me")
(br/generate-input-field "grafana-cloud-user" "Your grafana user name:" "user")
(br/generate-input-field "grafana-cloud-password" "Your grafana password:" "change-me")))
[(br/generate-br)]
(br/generate-button "generate-button" "Generate c4k yaml")))]
(br/generate-output "c4k-taiga-output" "Your c4k deployment.yaml:" "15")))
@ -68,14 +61,60 @@
:content
(generate-content)})
(defn config-from-document []
(let [issuer (br/get-content-from-element "issuer" :optional true)
taigas (br/get-content-from-element "taigas" :deserializer edn/read-string)
mon-cluster-name (br/get-content-from-element "mon-cluster-name" :optional true)
mon-cluster-stage (br/get-content-from-element "mon-cluster-stage" :optional true)
mon-cloud-url (br/get-content-from-element "mon-cloud-url" :optional true)]
(defn auth-from-document []
(let [postgres-db-user (br/get-content-from-element "postgres-db-user" )
postgres-db-password (br/get-content-from-element "postgres-db-password" )
mailer-user (br/get-content-from-element "mailer-user" )
mailer-pw (br/get-content-from-element "mailer-pw" )
django-superuser-username (br/get-content-from-element "django-superuser-username" )
django-superuser-password (br/get-content-from-element "django-superuser-password" )
django-superuser-email (br/get-content-from-element "django-superuser-email" )
rabbitmq-user (br/get-content-from-element "rabbitmq-user" )
rabbitmq-pw (br/get-content-from-element "rabbitmq-pw" )
rabbitmq-erlang-cookie (br/get-content-from-element "rabbitmq-erlang-cookie" )
taiga-secret-key (br/get-content-from-element "taiga-secret-key" )
grafana-cloud-user (br/get-content-from-element "grafana-cloud-user" :optional true)
grafana-cloud-password (br/get-content-from-element "grafana-cloud-password" :optional true)]
(merge
{:taigas taigas}
{:postgres-db-user postgres-db-user}
{:postgres-db-password postgres-db-password}
{:mailer-user mailer-user}
{:mailer-pw mailer-pw}
{:django-superuser-username django-superuser-username}
{:django-superuser-password django-superuser-password}
{:django-superuser-email django-superuser-email}
{:rabbitmq-user rabbitmq-user}
{:rabbitmq-pw rabbitmq-pw}
{:rabbitmq-erlang-cookie rabbitmq-erlang-cookie}
{:taiga-secret-key taiga-secret-key}
(when (some? grafana-cloud-user)
{:mon-auth {:grafana-cloud-user grafana-cloud-user
:grafana-cloud-password grafana-cloud-password}}))))
(defn config-from-document []
(let [issuer (br/get-content-from-element "issuer" :optional true)
fqdn (br/get-content-from-element "fqdn" :deserializer edn/read-string)
public-register-enabled (br/get-content-from-element "public-register-enabled" :deserializer edn/read-string)
enable-telemetry (br/get-content-from-element "enable-telemetry" :deserializer edn/read-string)
pv-storage-size-gb (br/get-content-from-element "pv-storage-size-gb" :deserializer edn/read-string)
storage-class-name (br/get-content-from-element "storage-class-name" :deserializer edn/read-string)
storage-media-size (br/get-content-from-element "storage-media-size" :deserializer edn/read-string)
storage-static-size (br/get-content-from-element "storage-static-size" :deserializer edn/read-string)
storage-async-rabbitmq-size (br/get-content-from-element "storage-async-rabbitmq-size" :deserializer edn/read-string)
storage-events-rabbitmq-size (br/get-content-from-element "storage-events-rabbitmq-size" :deserializer edn/read-string)
mon-cluster-name (br/get-content-from-element "mon-cluster-name" :optional true)
mon-cluster-stage (br/get-content-from-element "mon-cluster-stage" :optional true)
mon-cloud-url (br/get-content-from-element "mon-cloud-url" :optional true)]
(merge
{:fqdn fqdn}
{:public-register-enabled public-register-enabled}
{:enable-telemetry enable-telemetry}
{:pv-storage-size-gb pv-storage-size-gb}
{:storage-class-name storage-class-name}
{:storage-media-size storage-media-size}
{:storage-static-size storage-static-size}
{:storage-async-rabbitmq-size storage-async-rabbitmq-size}
{:storage-events-rabbitmq-size storage-events-rabbitmq-size}
(when (not (st/blank? issuer))
{:issuer issuer})
(when (some? mon-cluster-name)
@ -84,12 +123,32 @@
:grafana-cloud-url mon-cloud-url}}))))
(defn validate-all! []
(br/validate! "taigas" taiga/taigas? :deserializer edn/read-string)
(br/validate! "issuer" ::taiga/issuer :optional true)
(br/validate! "mon-cluster-name" ::mon/cluster-name :optional true)
(br/validate! "mon-cluster-stage" ::mon/cluster-stage :optional true)
(br/validate! "mon-cloud-url" ::mon/grafana-cloud-url :optional true)
(br/validate! "auth" taiga/auth? :deserializer edn/read-string)
(br/validate! "fqdn" ::taiga/fqdn )
(br/validate! "issuer" ::taiga/issuer :optional true)
(br/validate! "public-register-enabled" ::taiga/public-register-enabled :optional true)
(br/validate! "enable-telemetry" ::taiga/enable-telemetry :optional true)
(br/validate! "pv-storage-size-gb" ::postgres/pv-storage-size-gb :optional true)
(br/validate! "storage-class-name" ::taiga/storage-class-name :optional true)
(br/validate! "storage-media-size" ::taiga/storage-media-size :optional true)
(br/validate! "storage-static-size" ::taiga/storage-static-size :optional true)
(br/validate! "storage-async-rabbitmq-size" ::taiga/storage-async-rabbitmq-size :optional true)
(br/validate! "storage-events-rabbitmq-size" ::taiga/storage-events-rabbitmq-size :optional true)
(br/validate! "mon-cluster-name" ::mon/cluster-name :optional true)
(br/validate! "mon-cluster-stage" ::mon/cluster-stage :optional true)
(br/validate! "mon-cloud-url" ::mon/grafana-cloud-url :optional true)
(br/validate! "postgres-db-user" ::postgres/postgres-db-user )
(br/validate! "postgres-db-password" ::postgres/postgres-db-password )
(br/validate! "mailer-user" ::taiga/mailer-user )
(br/validate! "mailer-pw" ::taiga/mailer-pw )
(br/validate! "django-superuser-username" ::taiga/django-superuser-username )
(br/validate! "django-superuser-password" ::taiga/django-superuser-password )
(br/validate! "django-superuser-email" ::taiga/django-superuser-email )
(br/validate! "rabbitmq-user" ::taiga/rabbitmq-user )
(br/validate! "rabbitmq-pw" ::taiga/rabbitmq-pw )
(br/validate! "rabbitmq-erlang-cookie" ::taiga/rabbitmq-erlang-cookie )
(br/validate! "taiga-secret-key" ::taiga/taiga-secret-key )
(br/validate! "grafana-cloud-user" ::mon/grafana-cloud-user )
(br/validate! "grafana-cloud-password" ::mon/grafana-cloud-password )
(br/set-form-validated!))
(defn add-validate-listener [name]
@ -104,13 +163,33 @@
#(do (validate-all!)
(-> (cm/generate-common
(config-from-document)
(br/get-content-from-element "auth" :deserializer edn/read-string)
(auth-from-document)
core/config-defaults
core/k8s-objects)
(br/set-output!)))))
(add-validate-listener "taigas")
(add-validate-listener "fqdn")
(add-validate-listener "issuer")
(add-validate-listener "public-register-enabled")
(add-validate-listener "enable-telemetry")
(add-validate-listener "pv-storage-size-gb")
(add-validate-listener "storage-class-name")
(add-validate-listener "storage-media-size")
(add-validate-listener "storage-static-size")
(add-validate-listener "storage-async-rabbitmq-size")
(add-validate-listener "storage-events-rabbitmq-size")
(add-validate-listener "mon-cluster-name")
(add-validate-listener "mon-cluster-stage")
(add-validate-listener "mon-cloud-url")
(add-validate-listener "auth"))
(add-validate-listener "postgres-db-user")
(add-validate-listener "postgres-db-password")
(add-validate-listener "mailer-user")
(add-validate-listener "mailer-pw")
(add-validate-listener "django-superuser-username")
(add-validate-listener "django-superuser-password")
(add-validate-listener "django-superuser-email")
(add-validate-listener "rabbitmq-user")
(add-validate-listener "rabbitmq-pw")
(add-validate-listener "rabbitmq-erlang-cookie")
(add-validate-listener "taiga-secret-key")
(add-validate-listener "grafana-cloud-user")
(add-validate-listener "grafana-cloud-password"))

View file

@ -0,0 +1,74 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backup-restore
spec:
replicas: 0
selector:
matchLabels:
app: backup-restore
strategy:
type: Recreate
template:
metadata:
labels:
app: backup-restore
app.kubernetes.io/name: backup-restore
app.kubernetes.io/part-of: taiga
spec:
containers:
- image: domaindrivenarchitecture/c4k-taiga-backup
name: backup-app
imagePullPolicy: IfNotPresent
command: ["/entrypoint-start-and-wait.sh"]
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: postgres-config
key: postgres-db
- name: POSTGRES_HOST
value: "postgresql-service:5432"
- name: POSTGRES_SERVICE
value: "postgresql-service"
- name: POSTGRES_PORT
value: "5432"
- name: AWS_DEFAULT_REGION
value: eu-central-1
- name: AWS_ACCESS_KEY_ID_FILE
value: /var/run/secrets/backup-secrets/aws-access-key-id
- name: AWS_SECRET_ACCESS_KEY_FILE
value: /var/run/secrets/backup-secrets/aws-secret-access-key
- name: RESTIC_REPOSITORY
valueFrom:
configMapKeyRef:
name: backup-config
key: restic-repository
- name: RESTIC_PASSWORD_FILE
value: /var/run/secrets/backup-secrets/restic-password
- name: CERTIFICATE_FILE
value: ""
volumeMounts:
- name: taiga-media
mountPath: /media
readOnly: false
- name: backup-secret-volume
mountPath: /var/run/secrets/backup-secrets
readOnly: true
volumes:
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media-data
- name: backup-secret-volume
secret:
secretName: backup-secret

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: backup-config
labels:
app.kubernetes.io/name: backup
app.kubernetes.io/part-of: taiga
data:
restic-repository: restic-repository

View file

@ -0,0 +1,71 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: taiga-backup
labels:
app.kubernetes.part-of: taiga
spec:
schedule: "10 23 * * *"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: backup-app
image: domaindrivenarchitecture/c4k-taiga-backup
imagePullPolicy: IfNotPresent
command: ["/entrypoint.sh"]
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: postgres-config
key: postgres-db
- name: POSTGRES_HOST
value: "postgresql-service:5432"
- name: POSTGRES_SERVICE
value: "postgresql-service"
- name: POSTGRES_PORT
value: "5432"
- name: AWS_DEFAULT_REGION
value: eu-central-1
- name: AWS_ACCESS_KEY_ID_FILE
value: /var/run/secrets/backup-secrets/aws-access-key-id
- name: AWS_SECRET_ACCESS_KEY_FILE
value: /var/run/secrets/backup-secrets/aws-secret-access-key
- name: RESTIC_REPOSITORY
valueFrom:
configMapKeyRef:
name: backup-config
key: restic-repository
- name: RESTIC_PASSWORD_FILE
value: /var/run/secrets/backup-secrets/restic-password
- name: CERTIFICATE_FILE
value: ""
volumeMounts:
- name: taiga-media
mountPath: /media
readOnly: true
- name: backup-secret-volume
mountPath: /var/run/secrets/backup-secrets
readOnly: true
volumes:
- name: taiga-media
persistentVolumeClaim:
claimName: taiga-media-data
- name: backup-secret-volume
secret:
secretName: backup-secret
restartPolicy: OnFailure

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: backup-secret
type: Opaque
data:
aws-access-key-id: aws-access-key-id
aws-secret-access-key: aws-secret-access-key
restic-password: restic-password

View file

@ -20,7 +20,7 @@ spec:
image: taigaio/taiga-back:latest
imagePullPolicy: IfNotPresent
command: ["/bin/bash"]
args: ["-c", "source /opt/venv/bin/activate && CELERY_ENABLE=true python manage.py migrate && sleep 15 && python manage.py createsuperuser --noinput"]
args: ["-c", "source /opt/venv/bin/activate && CELERY_ENABLE=true python manage.py migrate && sleep 15"]
ports:
- name: http
containerPort: 8000

View file

@ -19,8 +19,7 @@
:kind "ConfigMap",
:metadata {:name "taiga-configmap", :namespace "default"},
:data
{:CELERY_ENABLED "false",
:ENABLE_TELEMETRY "false",
{:ENABLE_TELEMETRY "false",
:TAIGA_SITES_SCHEME "https",
:TAIGA_SITES_DOMAIN "taiga.test.meissa.de",
:TAIGA_SUBPATH "",
@ -31,8 +30,8 @@
:ENABLE_JIRA_IMPORTER "false",
:ENABLE_TRELLO_IMPORTER "false",
:RABBITMQ_DEFAULT_VHOST "taiga",
:SESSION_COOKIE_SECURE "false",
:CSRF_COOKIE_SECURE "false"}}
:SESSION_COOKIE_SECURE "False",
:CSRF_COOKIE_SECURE "False"}}
(cut/generate-configmap (yaml/load-as-edn "taiga-test/valid-config.yaml")))))
(deftest should-generate-pvc-taiga-media-data

View file

@ -1,6 +1,6 @@
taiga-secret-key: "some-key"
postgres-db-user: "forgejo"
postgres-db-password: "forgejo-db-password"
postgres-db-user: "taiga"
postgres-db-password: "taiga-db-password"
mailer-user: "mailer-user"
mailer-pw: "mailer-pw"
django-superuser-username: "taiga-admin"
@ -9,6 +9,9 @@ django-superuser-email: "some@example.com"
rabbitmq-user: "rabbit-user"
rabbitmq-pw: "rabbit-pw"
rabbitmq-erlang-cookie: "rabbit-erlang"
aws-access-key-id: "AWS_KEY_ID"
aws-secret-access-key: "AWS_KEY_SECRET"
restic-password: ""
mon-auth:
grafana-cloud-user: "user"
grafana-cloud-password: "password"

View file

@ -7,6 +7,7 @@ storage-media-size: 2
storage-static-size: 3
storage-async-rabbitmq-size: 4
storage-events-rabbitmq-size: 5
restic-repository: "repo-path"
mon-cfg:
grafana-cloud-url: "url-for-your-prom-remote-write-endpoint"
cluster-name: "jitsi"