@yunuseon/kayra 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -2
- package/ansible/.ansible-lint +13 -0
- package/ansible/ansible.cfg +12 -0
- package/ansible/playbooks/adopt.yml +35 -0
- package/ansible/playbooks/app.yml +21 -0
- package/ansible/playbooks/apps-prune.yml +30 -0
- package/ansible/playbooks/edge.yml +41 -0
- package/ansible/playbooks/nas.yml +47 -0
- package/ansible/playbooks/platform.yml +62 -0
- package/ansible/requirements.yml +7 -0
- package/ansible/roles/app/defaults/main.yml +9 -0
- package/ansible/roles/app/tasks/main.yml +107 -0
- package/ansible/roles/app/templates/app.env.j2 +9 -0
- package/ansible/roles/app/templates/casc-app.yaml.j2 +68 -0
- package/ansible/roles/backup-check/files/backup-check.service +9 -0
- package/ansible/roles/backup-check/files/backup-check.timer +10 -0
- package/ansible/roles/backup-check/tasks/main.yml +59 -0
- package/ansible/roles/backup-check/templates/backup-check.sh.j2 +117 -0
- package/ansible/roles/common/handlers/main.yml +16 -0
- package/ansible/roles/common/tasks/main.yml +100 -0
- package/ansible/roles/compose-stacks/tasks/main.yml +102 -0
- package/ansible/roles/docker/defaults/main.yml +3 -0
- package/ansible/roles/docker/files/buildkit-rootless +23 -0
- package/ansible/roles/docker/handlers/main.yml +12 -0
- package/ansible/roles/docker/tasks/main.yml +82 -0
- package/ansible/roles/edge-proxy/files/docker-compose.yml +13 -0
- package/ansible/roles/edge-proxy/handlers/main.yml +6 -0
- package/ansible/roles/edge-proxy/tasks/main.yml +34 -0
- package/ansible/roles/edge-proxy/templates/Caddyfile.j2 +25 -0
- package/ansible/roles/forgejo-config/tasks/main.yml +333 -0
- package/ansible/roles/platform/defaults/main.yml +76 -0
- package/ansible/roles/platform/tasks/main.yml +298 -0
- package/ansible/roles/platform/templates/authelia-users.yml.j2 +17 -0
- package/ansible/roles/platform/templates/platform.env.j2 +41 -0
- package/ansible/roles/restic-backup/files/docker-compose.postgres.yml +27 -0
- package/ansible/roles/restic-backup/files/docker-compose.yml +12 -0
- package/ansible/roles/restic-backup/handlers/main.yml +7 -0
- package/ansible/roles/restic-backup/tasks/main.yml +121 -0
- package/ansible/roles/restic-backup/templates/backup.env.j2 +9 -0
- package/ansible/roles/restic-backup/templates/backup.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/docker-compose.override.yml.j2 +26 -0
- package/ansible/roles/restic-backup/templates/follow-restore-test.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/restore-test.sh.j2 +51 -0
- package/ansible/roles/restic-backup/templates/root.crontab.j2 +6 -0
- package/ansible/roles/restic-backup/templates/ssh_config.j2 +12 -0
- package/ansible/roles/wireguard/handlers/main.yml +9 -0
- package/ansible/roles/wireguard/tasks/main.yml +33 -0
- package/ansible/roles/wireguard/templates/wg0.conf.j2 +12 -0
- package/bin/kayra.js +29 -0
- package/cli/dist/brand.js +41 -0
- package/cli/dist/cli.js +21 -0
- package/cli/dist/commands/doctor.js +79 -0
- package/cli/dist/commands/server.js +215 -0
- package/cli/dist/commands/status.js +43 -0
- package/cli/dist/fleet.js +134 -0
- package/cli/dist/interpret.js +98 -0
- package/cli/dist/load.js +49 -0
- package/cli/dist/main.js +94 -0
- package/cli/dist/plan.js +31 -0
- package/cli/dist/result.js +5 -0
- package/cloud-init.yml.tpl +14 -0
- package/compose/authelia/configuration.yml +138 -0
- package/compose/buildkit/buildkitd.toml +14 -0
- package/compose/docker-compose.acme-dns.yml +12 -0
- package/compose/docker-compose.ci.yml +156 -0
- package/compose/docker-compose.yml +83 -0
- package/compose/jenkins/Dockerfile +40 -0
- package/compose/jenkins/casc/00-platform.yaml +158 -0
- package/compose/jenkins/plugins.txt +97 -0
- package/compose/jenkins-agent/Dockerfile +24 -0
- package/compose/traefik/dynamic/authelia.yml +18 -0
- package/compose/traefik/dynamic/middlewares.yml +14 -0
- package/compose/traefik/dynamic/ratelimit.yml +21 -0
- package/compose/traefik/dynamic/tls.yml +9 -0
- package/compose/traefik/traefik.yml +46 -0
- package/hosts.conf +6 -0
- package/package.json +73 -4
- package/scripts/local/app-manifests.sh +26 -0
- package/scripts/local/authelia-dev-init.sh +58 -0
- package/scripts/local/certs.sh +55 -0
- package/scripts/local/home-image.sh +55 -0
- package/scripts/local/home-reach.sh +41 -0
- package/scripts/local/require-main.sh +38 -0
- package/scripts/local/setup-hosts.sh +30 -0
- package/scripts/server/app-purge +26 -0
- package/scripts/server/app-remove +30 -0
- package/scripts/server/jenkins-api +28 -0
- package/scripts/server/jenkins-app-apply +36 -0
- package/scripts/server/jenkins-reload +12 -0
- package/terraform/modules/hcloud-firewall/main.tf +54 -0
- package/terraform/modules/hcloud-firewall/outputs.tf +9 -0
- package/terraform/modules/hcloud-firewall/variables.tf +22 -0
- package/terraform/modules/hcloud-server/main.tf +62 -0
- package/terraform/modules/hcloud-server/outputs.tf +19 -0
- package/terraform/modules/hcloud-server/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/hcloud-server/variables.tf +50 -0
- package/terraform/modules/multipass-instance/main.tf +67 -0
- package/terraform/modules/multipass-instance/outputs.tf +19 -0
- package/terraform/modules/multipass-instance/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/multipass-instance/variables.tf +39 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Platform base — Traefik reverse proxy + shared network
|
|
3
|
+
# App-agnostic: knows nothing about what applications run on this platform.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
traefik:
|
|
8
|
+
image: traefik:v3.7.14
|
|
9
|
+
restart: unless-stopped
|
|
10
|
+
ports:
|
|
11
|
+
- "80:80"
|
|
12
|
+
- "443:443"
|
|
13
|
+
volumes:
|
|
14
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
15
|
+
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
|
16
|
+
- ./traefik/dynamic:/etc/traefik/dynamic:ro
|
|
17
|
+
- ./traefik/certs:/certs:ro
|
|
18
|
+
- acme-data:/acme
|
|
19
|
+
networks:
|
|
20
|
+
- public
|
|
21
|
+
- internal
|
|
22
|
+
labels:
|
|
23
|
+
# The dashboard used to ride on an unauthenticated published :8080.
|
|
24
|
+
# It speaks no auth of its own, which is exactly what forwardAuth is for.
|
|
25
|
+
- "traefik.enable=true"
|
|
26
|
+
- "traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN:-localhost}`)"
|
|
27
|
+
- "traefik.http.routers.dashboard.entrypoints=websecure"
|
|
28
|
+
- "traefik.http.routers.dashboard.tls.certresolver=le"
|
|
29
|
+
- "traefik.http.routers.dashboard.service=api@internal"
|
|
30
|
+
- "traefik.http.routers.dashboard.middlewares=authelia@file,security-headers@file"
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Authelia — single sign-on for services that sit behind Traefik.
|
|
34
|
+
#
|
|
35
|
+
# Deliberately NOT behind its own authelia@file middleware: that would be an
|
|
36
|
+
# infinite redirect. The portal has to stay reachable to authenticate anyone.
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
authelia:
|
|
39
|
+
image: authelia/authelia:4.39.28
|
|
40
|
+
restart: unless-stopped
|
|
41
|
+
environment:
|
|
42
|
+
- X_AUTHELIA_CONFIG=/config/configuration.yml
|
|
43
|
+
# Authelia does not expand ${VAR}; this filter is what makes
|
|
44
|
+
# {{ env "DOMAIN" }} work inside configuration.yml.
|
|
45
|
+
- X_AUTHELIA_CONFIG_FILTERS=template
|
|
46
|
+
- DOMAIN=${DOMAIN:-localhost}
|
|
47
|
+
- AUTH_POLICY=${AUTH_POLICY:-two_factor}
|
|
48
|
+
- AUTHELIA_SESSION_SECRET_FILE=/config/secrets/session
|
|
49
|
+
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/config/secrets/storage_encryption
|
|
50
|
+
# Empty in dev, which falls the notifier back to a file — see
|
|
51
|
+
# configuration.yml. The SMTP password is NOT an env var; it is read from
|
|
52
|
+
# /config/secrets/smtp_password by the config template.
|
|
53
|
+
- SMTP_ADDRESS=${AUTHELIA_SMTP_ADDRESS:-}
|
|
54
|
+
- SMTP_USERNAME=${AUTHELIA_SMTP_USERNAME:-}
|
|
55
|
+
- SMTP_SENDER=${AUTHELIA_SMTP_SENDER:-}
|
|
56
|
+
volumes:
|
|
57
|
+
- ./authelia:/config:ro
|
|
58
|
+
- authelia-data:/data
|
|
59
|
+
mem_limit: 128m
|
|
60
|
+
networks:
|
|
61
|
+
- internal
|
|
62
|
+
labels:
|
|
63
|
+
- "traefik.enable=true"
|
|
64
|
+
- "traefik.docker.network=platform-internal"
|
|
65
|
+
- "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN:-localhost}`)"
|
|
66
|
+
- "traefik.http.routers.authelia.entrypoints=websecure"
|
|
67
|
+
- "traefik.http.routers.authelia.tls.certresolver=le"
|
|
68
|
+
- "traefik.http.services.authelia.loadbalancer.server.port=9091"
|
|
69
|
+
|
|
70
|
+
# Two networks, so an app can reach Traefik's routes and nothing else of the
|
|
71
|
+
# platform. Apps join platform-public; the platform's own services live on
|
|
72
|
+
# platform-internal, where only Traefik joins them.
|
|
73
|
+
networks:
|
|
74
|
+
public:
|
|
75
|
+
name: platform-public
|
|
76
|
+
internal:
|
|
77
|
+
name: platform-internal
|
|
78
|
+
|
|
79
|
+
volumes:
|
|
80
|
+
acme-data:
|
|
81
|
+
# Passkeys live here. Losing this volume means every credential is gone and
|
|
82
|
+
# everyone re-enrols — back it up.
|
|
83
|
+
authelia-data:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Jenkins Controller — LTS with Docker CLI, Compose, buildctl and JCasC
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
FROM jenkins/jenkins:2.580.1-lts-jdk21
|
|
6
|
+
|
|
7
|
+
USER root
|
|
8
|
+
|
|
9
|
+
# renovate: datasource=github-releases depName=moby/buildkit
|
|
10
|
+
ARG BUILDKIT_VERSION=v0.34.0
|
|
11
|
+
|
|
12
|
+
# Install Docker CLI + Compose plugin
|
|
13
|
+
RUN apt-get update && \
|
|
14
|
+
apt-get install -y --no-install-recommends \
|
|
15
|
+
ca-certificates curl gnupg jq && \
|
|
16
|
+
install -m 0755 -d /etc/apt/keyrings && \
|
|
17
|
+
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
|
|
18
|
+
chmod a+r /etc/apt/keyrings/docker.gpg && \
|
|
19
|
+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
|
20
|
+
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
|
21
|
+
> /etc/apt/sources.list.d/docker.list && \
|
|
22
|
+
apt-get update && \
|
|
23
|
+
apt-get install -y --no-install-recommends \
|
|
24
|
+
docker-ce-cli docker-compose-plugin && \
|
|
25
|
+
arch="$(dpkg --print-architecture)" && \
|
|
26
|
+
curl -fsSL "https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/buildkit-${BUILDKIT_VERSION}.linux-${arch}.tar.gz" \
|
|
27
|
+
| tar -xz -C /usr/local bin/buildctl && \
|
|
28
|
+
rm -rf /var/lib/apt/lists/*
|
|
29
|
+
|
|
30
|
+
USER jenkins
|
|
31
|
+
|
|
32
|
+
# Install plugins
|
|
33
|
+
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
|
|
34
|
+
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt
|
|
35
|
+
|
|
36
|
+
# JCasC fragments are bind-mounted, not baked in; CASC_JENKINS_CONFIG is set in
|
|
37
|
+
# compose. That keeps config out of the image and out of the jenkins-home volume.
|
|
38
|
+
# notifyCommit without a token, but only to trigger polling: how Forgejo tells
|
|
39
|
+
# Jenkins about a push, over platform-internal (Traefik does not route it).
|
|
40
|
+
ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false -Dhudson.plugins.git.GitStatus.NOTIFY_COMMIT_ACCESS_CONTROL=disabled-for-polling"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Jenkins Configuration as Code — platform fragment (app-agnostic).
|
|
3
|
+
#
|
|
4
|
+
# Owns:
|
|
5
|
+
# - Jenkins setup, security realm, authorization
|
|
6
|
+
# - The agent node
|
|
7
|
+
# - Git credentials (for cloning the app repo)
|
|
8
|
+
# - GitHub: push-webhook secret + API token for commit statuses
|
|
9
|
+
# - Jenkins URL
|
|
10
|
+
#
|
|
11
|
+
# Also owns the app-agnostic jobs: platform-registry-gc and rollback work off
|
|
12
|
+
# the release records every app's <app>/deploy job writes (the platform
|
|
13
|
+
# library's appDeploy) and start that job with a REF. platform-renovate exists
|
|
14
|
+
# only where the server has repos to keep up to date (PLATFORM_RENOVATE).
|
|
15
|
+
#
|
|
16
|
+
# Does NOT own any app-specific credentials (Postgres, ...) or app pipelines.
|
|
17
|
+
# Each app renders its own 10-<app>.yaml next to this file and applies it with
|
|
18
|
+
# bin/jenkins-app-apply. JCasC merges every YAML in this directory.
|
|
19
|
+
#
|
|
20
|
+
# Git remote comes from the platform .env (rendered by Ansible).
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
jenkins:
|
|
24
|
+
systemMessage: "Platform CI/CD — Managed by JCasC"
|
|
25
|
+
numExecutors: 1
|
|
26
|
+
|
|
27
|
+
securityRealm:
|
|
28
|
+
local:
|
|
29
|
+
allowsSignup: false
|
|
30
|
+
users:
|
|
31
|
+
- id: "${JENKINS_ADMIN_USER:-admin}"
|
|
32
|
+
password: "${JENKINS_ADMIN_PASSWORD:-admin}"
|
|
33
|
+
|
|
34
|
+
authorizationStrategy:
|
|
35
|
+
loggedInUsersCanDoAnything:
|
|
36
|
+
allowAnonymousRead: false
|
|
37
|
+
|
|
38
|
+
nodes:
|
|
39
|
+
- permanent:
|
|
40
|
+
name: "builder"
|
|
41
|
+
labelString: "buildkit linux"
|
|
42
|
+
numExecutors: 2
|
|
43
|
+
remoteFS: "/home/jenkins/agent"
|
|
44
|
+
launcher:
|
|
45
|
+
inbound:
|
|
46
|
+
workDirSettings:
|
|
47
|
+
disabled: false
|
|
48
|
+
internalDir: "remoting"
|
|
49
|
+
retentionStrategy: "always"
|
|
50
|
+
|
|
51
|
+
credentials:
|
|
52
|
+
system:
|
|
53
|
+
domainCredentials:
|
|
54
|
+
- credentials:
|
|
55
|
+
- usernamePassword:
|
|
56
|
+
scope: GLOBAL
|
|
57
|
+
id: "git-credentials"
|
|
58
|
+
description: "Git credentials for cloning repositories"
|
|
59
|
+
username: "git"
|
|
60
|
+
password: "${readFile:/run/platform-secrets/GIT_TOKEN}"
|
|
61
|
+
- string:
|
|
62
|
+
scope: GLOBAL
|
|
63
|
+
id: "github-token"
|
|
64
|
+
description: "Same PAT as git-credentials; needs Commit statuses: write"
|
|
65
|
+
secret: "${readFile:/run/platform-secrets/GIT_TOKEN}"
|
|
66
|
+
- string:
|
|
67
|
+
scope: GLOBAL
|
|
68
|
+
id: "renovate-token"
|
|
69
|
+
description: "PAT for Renovate: Contents + Pull requests read & write"
|
|
70
|
+
secret: "${readFile:/run/platform-secrets/RENOVATE_TOKEN}"
|
|
71
|
+
- string:
|
|
72
|
+
scope: GLOBAL
|
|
73
|
+
id: "github-webhook-secret"
|
|
74
|
+
description: "HMAC secret GitHub signs push webhooks with"
|
|
75
|
+
secret: "${GITHUB_WEBHOOK_SECRET:-}"
|
|
76
|
+
|
|
77
|
+
unclassified:
|
|
78
|
+
# The pipeline every app runs. Trusted (outside the sandbox), loaded from
|
|
79
|
+
# PLATFORM_REF (the release tag, v<version>; a test server may set a branch) and jobs cannot
|
|
80
|
+
# pick another version, so a pushed app branch never changes what a pipeline
|
|
81
|
+
# does. See ci/jenkins-lib/.
|
|
82
|
+
globalLibraries:
|
|
83
|
+
libraries:
|
|
84
|
+
- name: "platform"
|
|
85
|
+
defaultVersion: "${PLATFORM_REF}"
|
|
86
|
+
allowVersionOverride: false
|
|
87
|
+
implicit: false
|
|
88
|
+
retriever:
|
|
89
|
+
modernSCM:
|
|
90
|
+
libraryPath: "ci/jenkins-lib/"
|
|
91
|
+
scm:
|
|
92
|
+
git:
|
|
93
|
+
remote: "${JENKINS_GIT_REPO}"
|
|
94
|
+
credentialsId: "git-credentials"
|
|
95
|
+
location:
|
|
96
|
+
url: "https://jenkins.${DOMAIN:-localhost}/"
|
|
97
|
+
# Webhook lands on /github-webhook/. Hooks are registered by hand in the
|
|
98
|
+
# repo settings, not managed from here.
|
|
99
|
+
gitHubPluginConfig:
|
|
100
|
+
configs:
|
|
101
|
+
- name: "GitHub"
|
|
102
|
+
apiUrl: "https://api.github.com"
|
|
103
|
+
credentialsId: "github-token"
|
|
104
|
+
manageHooks: false
|
|
105
|
+
hookSecretConfigs:
|
|
106
|
+
- credentialsId: "github-webhook-secret"
|
|
107
|
+
|
|
108
|
+
# Platform jobs run pipelines from PLATFORM_REF of this repo, never from a pushed branch.
|
|
109
|
+
jobs:
|
|
110
|
+
- script: |
|
|
111
|
+
def fromPlatform = { String path ->
|
|
112
|
+
return {
|
|
113
|
+
cpsScm {
|
|
114
|
+
scm {
|
|
115
|
+
git {
|
|
116
|
+
remote {
|
|
117
|
+
url("${JENKINS_GIT_REPO}")
|
|
118
|
+
credentials("git-credentials")
|
|
119
|
+
}
|
|
120
|
+
branch("${PLATFORM_REF}")
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
scriptPath(path)
|
|
124
|
+
lightweight(false)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pipelineJob("platform-registry-gc") {
|
|
130
|
+
displayName("Platform Registry GC")
|
|
131
|
+
description("Nightly: deletes registry images no recent release of any app references, then prunes the BuildKit cache and host images. See ci/registry-gc.sh.")
|
|
132
|
+
definition(fromPlatform("ci/registry-gc.Jenkinsfile"))
|
|
133
|
+
triggers {
|
|
134
|
+
cron("H 3 * * *")
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pipelineJob("rollback") {
|
|
139
|
+
displayName("Rollback")
|
|
140
|
+
description("Redeploys an earlier release of an app: pick the app, pick a release, done. Hands the SHA to <app>/deploy.")
|
|
141
|
+
definition(fromPlatform("ci/rollback.Jenkinsfile"))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ("${PLATFORM_RENOVATE:-false}" == "true") {
|
|
145
|
+
pipelineJob("platform-renovate") {
|
|
146
|
+
displayName("Platform Renovate")
|
|
147
|
+
description("On renovate_schedule (weekly by default): opens PRs that bump pinned versions. Config: renovate.json. See ci/renovate.Jenkinsfile.")
|
|
148
|
+
// Declared here as well: a JCasC reload regenerates the job and drops
|
|
149
|
+
// parameters only the pipeline declares.
|
|
150
|
+
parameters {
|
|
151
|
+
booleanParam("DRY_RUN", false, "Log what would change, open no PRs")
|
|
152
|
+
}
|
|
153
|
+
definition(fromPlatform("ci/renovate.Jenkinsfile"))
|
|
154
|
+
triggers {
|
|
155
|
+
cron("${RENOVATE_SCHEDULE:-H 4 * * 1}")
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Every plugin, dependencies included, at the version prod runs: without the
|
|
2
|
+
# dependencies pinned, jenkins-plugin-cli resolves them to whatever is newest
|
|
3
|
+
# on the day the image is built. Renovate proposes updates.
|
|
4
|
+
antisamy-markup-formatter:173.v680e3a_b_69ff3
|
|
5
|
+
apache-httpcomponents-client-4-api:4.5.14-269.vfa_2321039a_83
|
|
6
|
+
apache-httpcomponents-client-5-api:5.6.4-207.vd5b_a_c15e4de5
|
|
7
|
+
asm-api:9.10.1-216.va_9256d3b_844b_
|
|
8
|
+
authentication-tokens:1.144.v5ff4a_5ec5c33
|
|
9
|
+
bootstrap5-api:5.3.8-1048.va_c299057e35c
|
|
10
|
+
bouncycastle-api:2.30.1.86.1-306.vfc711fa_36d5f
|
|
11
|
+
branch-api:2.1303.v9f3b_95dc329d
|
|
12
|
+
caffeine-api:3.3.0-210.v71b_2985ce530
|
|
13
|
+
checks-api:422.vc7e8b_51488c6
|
|
14
|
+
cloud-stats:423.v63f017a_f582c
|
|
15
|
+
cloudbees-folder:6.1106.v3a_d9a_6d2465e
|
|
16
|
+
commons-compress-api:1.28.0-95.vb_e5e24f83645
|
|
17
|
+
commons-lang3-api:3.21.0-122.v6a_ea_4fd34049
|
|
18
|
+
commons-text-api:1.15.0-218.va_61573470393
|
|
19
|
+
configuration-as-code:2133.ve570217cdb_cd
|
|
20
|
+
credentials-binding:728.v902a_273b_8947
|
|
21
|
+
credentials:1511.v2e3cb_0008ef0
|
|
22
|
+
display-url-api:2.217.va_6b_de84cc74b_
|
|
23
|
+
docker-commons:477.v289085a_b_6896
|
|
24
|
+
docker-java-api:3.7.1-136.v5d70f77a_c3d6
|
|
25
|
+
docker-plugin:1331.v2c036a_dc63fc
|
|
26
|
+
docker-workflow:653.v2f2c08eff0ec
|
|
27
|
+
durable-task:686.v80ff80875b_82
|
|
28
|
+
echarts-api:6.1.0-1321.v86c4c5114494
|
|
29
|
+
eddsa-api:0.3.0.1-29.v67e9a_1c969b_b_
|
|
30
|
+
font-awesome-api:7.3.1-1013.v0835a_879ec6d
|
|
31
|
+
git-client:6.6.1
|
|
32
|
+
git:5.10.1
|
|
33
|
+
github-api:1.330-492.v3941a_032db_2a_
|
|
34
|
+
github:1.47.0
|
|
35
|
+
gson-api:2.14.0-201.v8eefe5515533
|
|
36
|
+
instance-identity:203.v15e81a_1b_7a_38
|
|
37
|
+
ionicons-api:94.vcc3065403257
|
|
38
|
+
jackson-annotations2-api:2.22-19.v10a_a_582ea_26e
|
|
39
|
+
jackson2-api:2.22.3-447.v6a_93ce7d2c82
|
|
40
|
+
jackson3-api:3.2.3-101.v69fc84453a_8e
|
|
41
|
+
jakarta-activation-api:2.1.4-1
|
|
42
|
+
jakarta-mail-api:2.1.5-1
|
|
43
|
+
jakarta-xml-bind-api:4.0.9-19.v2b_a_5b_44d9a_1c
|
|
44
|
+
javax-activation-api:1.2.0-8
|
|
45
|
+
jaxb:2.3.9-143.v5979df3304e6
|
|
46
|
+
job-dsl:3732.v9a_c49a_61a_313
|
|
47
|
+
joda-time-api:2.15.0-208.v6b_084e7a_d9f0
|
|
48
|
+
jquery3-api:3.7.1-687.v68d468e40b_30
|
|
49
|
+
json-api:20260814-226.v20f9685d642c
|
|
50
|
+
json-path-api:3.0.0-218.vcd4dd1355de2
|
|
51
|
+
junit:1434.vde2d24df0c4c
|
|
52
|
+
mailer:534.v1b_36f5864073
|
|
53
|
+
matrix-project:928.v1effc3f3a_236
|
|
54
|
+
mina-sshd-api-common:2.19.0-192.v2b_a_7b_2c1dc71
|
|
55
|
+
mina-sshd-api-core:2.19.0-192.v2b_a_7b_2c1dc71
|
|
56
|
+
okhttp-api:5.3.2-200.vedb_720a_cf1f8
|
|
57
|
+
pipeline-build-step:601.v6d4c6d1a_9dc7
|
|
58
|
+
pipeline-graph-analysis:254.v0f63a_a_447dca_
|
|
59
|
+
pipeline-graph-view:1053.v9df19e6ceed2
|
|
60
|
+
pipeline-groovy-lib:806.v408277b_33d1d
|
|
61
|
+
pipeline-input-step:560.v56198a_642157
|
|
62
|
+
pipeline-milestone-step:152.v6e22b_8cfc66c
|
|
63
|
+
pipeline-model-api:2.2293.v6e7193cec599
|
|
64
|
+
pipeline-model-definition:2.2293.v6e7193cec599
|
|
65
|
+
pipeline-model-extensions:2.2293.v6e7193cec599
|
|
66
|
+
pipeline-rest-api:2.41
|
|
67
|
+
pipeline-stage-step:345.va_96187909426
|
|
68
|
+
pipeline-stage-tags-metadata:2.2293.v6e7193cec599
|
|
69
|
+
pipeline-stage-view:2.41
|
|
70
|
+
pipeline-utility-steps:3.810.va_7672d206740
|
|
71
|
+
plain-credentials:199.v9f8e1f741799
|
|
72
|
+
plugin-util-api:7.1341.v039f146993d9
|
|
73
|
+
prism-api:1.30.0-741.v034eb_0b_0a_a_fa_
|
|
74
|
+
resource-disposer:0.25
|
|
75
|
+
scm-api:728.vc30dcf7a_0df5
|
|
76
|
+
script-security:1433.v17c58a_e45cc1
|
|
77
|
+
snakeyaml-api:2.7-152.vc672728cf536
|
|
78
|
+
snakeyaml-engine-api:3.2-18.v885ed7ff698f
|
|
79
|
+
ssh-credentials:372.va_250881b_08cd
|
|
80
|
+
ssh-slaves:3.1097.v868116049892
|
|
81
|
+
structs:362.va_b_695ef4fdf9
|
|
82
|
+
timestamper:1.30
|
|
83
|
+
token-macro:477.vd4f0dc3cb_cf1
|
|
84
|
+
trilead-api:2.284.v1974ea_324382
|
|
85
|
+
variant:70.va_d9f17f859e0
|
|
86
|
+
woodstox-core-api:7.3.0-16.v017f30ed004c
|
|
87
|
+
workflow-aggregator:608.v67378e9d3db_1
|
|
88
|
+
workflow-api:1413.v2ff1a_5e720fa_
|
|
89
|
+
workflow-basic-steps:1098.v808b_fd7f8cf4
|
|
90
|
+
workflow-cps:4391.v652b_b_b_1b_7c00
|
|
91
|
+
workflow-durable-task-step:1479.v56e587f413a_7
|
|
92
|
+
workflow-job:1609.v8926e7962024
|
|
93
|
+
workflow-multibranch:842.v3a_b_59b_57b_e6e
|
|
94
|
+
workflow-scm-step:487.v41313a_96b_65d
|
|
95
|
+
workflow-step-api:724.v538c2362b_dfb_
|
|
96
|
+
workflow-support:1015.v785e5a_b_b_8b_22
|
|
97
|
+
ws-cleanup:0.49
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Jenkins Inbound Agent — git + buildctl + jq, nothing app-specific.
|
|
3
|
+
#
|
|
4
|
+
# Deliberately has NO docker CLI and no docker socket: it runs code from
|
|
5
|
+
# arbitrary branches, and the host's socket would make that host root. Images
|
|
6
|
+
# are built by buildkitd over TCP. No language toolchain either: apps build and
|
|
7
|
+
# test inside their Dockerfiles, so a new app never changes this image.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
FROM jenkins/inbound-agent:3386.v353e57a_1b_ea_0-1-jdk21
|
|
11
|
+
|
|
12
|
+
USER root
|
|
13
|
+
|
|
14
|
+
# renovate: datasource=github-releases depName=moby/buildkit
|
|
15
|
+
ARG BUILDKIT_VERSION=v0.34.0
|
|
16
|
+
|
|
17
|
+
RUN apt-get update && \
|
|
18
|
+
apt-get install -y --no-install-recommends ca-certificates curl jq && \
|
|
19
|
+
arch="$(dpkg --print-architecture)" && \
|
|
20
|
+
curl -fsSL "https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/buildkit-${BUILDKIT_VERSION}.linux-${arch}.tar.gz" \
|
|
21
|
+
| tar -xz -C /usr/local bin/buildctl && \
|
|
22
|
+
rm -rf /var/lib/apt/lists/*
|
|
23
|
+
|
|
24
|
+
USER jenkins
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Dynamic Traefik configuration — Authelia forward authentication.
|
|
2
|
+
# Referenced from router labels as `authelia@file`.
|
|
3
|
+
http:
|
|
4
|
+
middlewares:
|
|
5
|
+
authelia:
|
|
6
|
+
forwardAuth:
|
|
7
|
+
address: 'http://authelia:9091/api/authz/forward-auth'
|
|
8
|
+
# Safe because neither entrypoint in traefik.yml sets
|
|
9
|
+
# forwardedHeaders.trustedIPs, so Traefik overwrites any client-supplied
|
|
10
|
+
# X-Forwarded-* before this middleware runs. Do not add trustedIPs.
|
|
11
|
+
trustForwardHeader: true
|
|
12
|
+
# The regex form strips every matching header from the inbound request
|
|
13
|
+
# before copying Authelia's back, closing the gap where Authelia
|
|
14
|
+
# returns no value for one of them and a client-supplied one survives.
|
|
15
|
+
authResponseHeadersRegex: '^Remote-(User|Groups|Email|Name)$'
|
|
16
|
+
# Traefik warns on every request without this. Authelia's auth
|
|
17
|
+
# responses are small; an unbounded body is a memory-exhaustion vector.
|
|
18
|
+
maxResponseBodySize: 8192
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Dynamic Traefik configuration — middlewares
|
|
2
|
+
http:
|
|
3
|
+
middlewares:
|
|
4
|
+
# Security headers for all services
|
|
5
|
+
security-headers:
|
|
6
|
+
headers:
|
|
7
|
+
browserXssFilter: true
|
|
8
|
+
contentTypeNosniff: true
|
|
9
|
+
frameDeny: true
|
|
10
|
+
sslRedirect: false
|
|
11
|
+
customFrameOptionsValue: "SAMEORIGIN"
|
|
12
|
+
stsSeconds: 31536000
|
|
13
|
+
stsIncludeSubdomains: true
|
|
14
|
+
referrerPolicy: "strict-origin-when-cross-origin"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Dynamic Traefik configuration — rate limiting.
|
|
2
|
+
http:
|
|
3
|
+
middlewares:
|
|
4
|
+
# The trial-request endpoint is public and unauthenticated by design: it is
|
|
5
|
+
# how strangers reach the club. That also makes it the one spam vector in
|
|
6
|
+
# the system, so it gets a ceiling.
|
|
7
|
+
#
|
|
8
|
+
# No sourceCriterion: the default keys on the client's remote address,
|
|
9
|
+
# which is correct when Traefik is the edge. An ipStrategy.depth here would
|
|
10
|
+
# expect an X-Forwarded-For chain that does not exist for a direct client,
|
|
11
|
+
# and the limit silently never engages.
|
|
12
|
+
#
|
|
13
|
+
# Values are empirical, not arithmetic: shorter periods did not actually
|
|
14
|
+
# throttle in testing, this regime does. 20 per 10 minutes with a burst of
|
|
15
|
+
# 5 is far more than a human filling in a form once, and caps a spammer at
|
|
16
|
+
# roughly 3k/day.
|
|
17
|
+
trial-ratelimit:
|
|
18
|
+
rateLimit:
|
|
19
|
+
average: 20
|
|
20
|
+
period: "10m"
|
|
21
|
+
burst: 5
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Dynamic TLS — file-based certificates for local + VM development.
|
|
2
|
+
# Generated by `make certs [VM_DOMAIN=...]` (mkcert under the hood).
|
|
3
|
+
# In prod, this directory is empty and ACME (Let's Encrypt) handles certs
|
|
4
|
+
# automatically. Traefik logs a warning for missing files but routes still
|
|
5
|
+
# work via the entrypoint's default certResolver.
|
|
6
|
+
tls:
|
|
7
|
+
certificates:
|
|
8
|
+
- certFile: /certs/cert.pem
|
|
9
|
+
keyFile: /certs/key.pem
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Traefik static configuration
|
|
2
|
+
api:
|
|
3
|
+
dashboard: true
|
|
4
|
+
# No insecure listener: the dashboard is served through a normal router on
|
|
5
|
+
# traefik.<DOMAIN>, behind Authelia. See docker-compose.yml.
|
|
6
|
+
insecure: false
|
|
7
|
+
|
|
8
|
+
entryPoints:
|
|
9
|
+
web:
|
|
10
|
+
address: ":80"
|
|
11
|
+
http:
|
|
12
|
+
redirections:
|
|
13
|
+
entryPoint:
|
|
14
|
+
to: websecure
|
|
15
|
+
scheme: https
|
|
16
|
+
permanent: true
|
|
17
|
+
websecure:
|
|
18
|
+
address: ":443"
|
|
19
|
+
http:
|
|
20
|
+
tls:
|
|
21
|
+
certResolver: le
|
|
22
|
+
# Every router, apps included: HSTS on half the hosts protects nothing.
|
|
23
|
+
middlewares:
|
|
24
|
+
- security-headers@file
|
|
25
|
+
|
|
26
|
+
certificatesResolvers:
|
|
27
|
+
le:
|
|
28
|
+
acme:
|
|
29
|
+
# Traefik does not expand env vars in its static file; the platform role
|
|
30
|
+
# substitutes acme_email on copy. Local dev never registers.
|
|
31
|
+
email: "acme@example.com"
|
|
32
|
+
storage: /acme/acme.json
|
|
33
|
+
httpChallenge:
|
|
34
|
+
entryPoint: web
|
|
35
|
+
|
|
36
|
+
providers:
|
|
37
|
+
docker:
|
|
38
|
+
endpoint: "unix:///var/run/docker.sock"
|
|
39
|
+
exposedByDefault: false
|
|
40
|
+
network: platform-public
|
|
41
|
+
file:
|
|
42
|
+
directory: /etc/traefik/dynamic
|
|
43
|
+
watch: true
|
|
44
|
+
|
|
45
|
+
log:
|
|
46
|
+
level: INFO
|
package/hosts.conf
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Platform subdomains to add to /etc/hosts and to the mkcert SAN list.
|
|
2
|
+
# App subdomains live in each app's app.yml; apps may not claim these.
|
|
3
|
+
# One subdomain per line. Read by infrastructure/scripts/local/setup-hosts.sh and infrastructure/scripts/local/certs.sh.
|
|
4
|
+
jenkins
|
|
5
|
+
auth
|
|
6
|
+
traefik
|
package/package.json
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunuseon/kayra",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A self-hosted app platform and the CLI that runs it: Traefik, Authelia, Jenkins, a registry and rootless BuildKit per server, set up with OpenTofu and Ansible.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Yunus Öztürk",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/yunuseon/kayra.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"kayra": "bin/kayra.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"cli/dist/",
|
|
21
|
+
"ansible/",
|
|
22
|
+
"compose/",
|
|
23
|
+
"terraform/",
|
|
24
|
+
"scripts/",
|
|
25
|
+
"cloud-init.yml.tpl",
|
|
26
|
+
"hosts.conf"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"provenance": true
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"kayra": "node cli/src/main.ts",
|
|
34
|
+
"build": "tsc -p cli/tsconfig.build.json",
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"typecheck": "tsc -p cli --noEmit",
|
|
37
|
+
"lint": "cd cli && eslint --flag unstable_native_nodejs_ts_config .",
|
|
38
|
+
"test": "node --test 'cli/test/**/*.test.ts'"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@optique/core": "1.3.3",
|
|
42
|
+
"@optique/run": "1.3.3",
|
|
43
|
+
"yaml": "2.9.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@eslint/js": "10.0.1",
|
|
47
|
+
"@tsconfig/node24": "24.0.5",
|
|
48
|
+
"@tsconfig/strictest": "2.0.8",
|
|
49
|
+
"@types/node": "24.19.1",
|
|
50
|
+
"eslint": "10.12.0",
|
|
51
|
+
"eslint-plugin-functional": "10.0.1",
|
|
52
|
+
"typescript": "6.0.3",
|
|
53
|
+
"typescript-eslint": "8.71.1"
|
|
54
|
+
},
|
|
55
|
+
"overrides": {
|
|
56
|
+
"eslint-plugin-functional": {
|
|
57
|
+
"deepmerge-ts": "8.0.2"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/yunuseon/kayra#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/yunuseon/kayra/issues"
|
|
63
|
+
},
|
|
64
|
+
"keywords": [
|
|
65
|
+
"paas",
|
|
66
|
+
"self-hosted",
|
|
67
|
+
"platform",
|
|
68
|
+
"traefik",
|
|
69
|
+
"jenkins",
|
|
70
|
+
"ansible",
|
|
71
|
+
"opentofu",
|
|
72
|
+
"hetzner",
|
|
73
|
+
"docker-compose"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Print the path of every app.yml a server runs, one per line, from the
|
|
3
|
+
# platform's cache of each app's repo (.cache/apps/<name>, the same clone the
|
|
4
|
+
# app role reads). A missing clone is
|
|
5
|
+
# fetched with the operator's own git credentials (kayra doctor checks them).
|
|
6
|
+
#
|
|
7
|
+
# Usage: FLEET_ROOT=<fleet> bash scripts/local/app-manifests.sh [--subdomains] <server-name>
|
|
8
|
+
# --subdomains print the apps' subdomains instead (for certs and /etc/hosts)
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
FLEET_ROOT="${FLEET_ROOT:?the directory of the fleet (servers/), set by kayra}"
|
|
12
|
+
subdomains=false
|
|
13
|
+
[ "${1:-}" != "--subdomains" ] || { subdomains=true; shift; }
|
|
14
|
+
name="${1:?Usage: app-manifests.sh [--subdomains] <server-name>}"
|
|
15
|
+
descriptor="${FLEET_ROOT}/servers/${name}/server.yml"
|
|
16
|
+
|
|
17
|
+
yq -o json -I0 '.apps // [] | .[]' "$descriptor" | while read -r app; do
|
|
18
|
+
path="$(jq -r '.path // "."' <<< "$app")"
|
|
19
|
+
repo="$(jq -r '.repo' <<< "$app")"
|
|
20
|
+
clone="${FLEET_ROOT}/.cache/apps/$(jq -r '.name' <<< "$app")"
|
|
21
|
+
if [ ! -d "${clone}/.git" ]; then
|
|
22
|
+
git clone -q --depth 1 --branch "$(jq -r '.ref // "main"' <<< "$app")" "$repo" "$clone"
|
|
23
|
+
fi
|
|
24
|
+
manifest="${clone}/${path}/app.yml"
|
|
25
|
+
if [ "$subdomains" = true ]; then yq '.subdomains[]' "$manifest"; else echo "$manifest"; fi
|
|
26
|
+
done
|