@yunuseon/kayra 0.7.1 → 0.8.1
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/README.md +5 -5
- package/ansible/playbooks/apps-prune.yml +6 -6
- package/ansible/playbooks/platform.yml +5 -5
- package/ansible/roles/app/tasks/main.yml +26 -14
- package/ansible/roles/app/templates/app.env.j2 +1 -1
- package/ansible/roles/common/tasks/main.yml +1 -1
- package/ansible/roles/forgejo-config/tasks/main.yml +4 -9
- package/ansible/roles/platform/defaults/main.yml +8 -15
- package/ansible/roles/platform/tasks/main.yml +96 -63
- package/ansible/roles/platform/templates/platform.env.j2 +3 -11
- package/cli/dist/brand.js +0 -3
- package/cli/dist/cli.js +10 -1
- package/cli/dist/commands/server.js +16 -2
- package/cli/dist/deployer/app.js +47 -0
- package/cli/dist/deployer/brand.js +8 -0
- package/cli/dist/deployer/builder.js +196 -0
- package/cli/dist/deployer/bundle.js +49 -0
- package/cli/dist/deployer/compose.js +26 -0
- package/cli/dist/deployer/cron.js +44 -0
- package/cli/dist/deployer/ctl.js +77 -0
- package/cli/dist/deployer/deployer.js +284 -0
- package/cli/dist/deployer/forge.js +25 -0
- package/cli/dist/deployer/http.js +22 -0
- package/cli/dist/deployer/main.js +19 -0
- package/cli/dist/deployer/page.js +32 -0
- package/cli/dist/deployer/process.js +31 -0
- package/cli/dist/deployer/queue.js +28 -0
- package/cli/dist/deployer/records.js +48 -0
- package/cli/dist/deployer/run.js +76 -0
- package/cli/dist/deployer/store.js +31 -0
- package/cli/dist/deployer/webhook.js +31 -0
- package/cli/dist/fleet.js +1 -1
- package/cli/dist/main.js +11 -7
- package/cli/dist/settings.js +6 -10
- package/compose/authelia/configuration.yml +3 -3
- package/compose/deployer/Dockerfile.builder +37 -0
- package/compose/deployer/Dockerfile.deployer +40 -0
- package/compose/deployer/kayra-deployer +6 -0
- package/compose/docker-compose.ci.yml +71 -79
- package/hosts.conf +2 -2
- package/package.json +2 -3
- package/schema/defaults.json +2 -14
- package/schema/server.json +1 -1
- package/schema/vars-platform.json +2 -14
- package/scripts/local/require-main.sh +5 -1
- package/scripts/platform/compose-policy.sh +84 -0
- package/scripts/platform/registry-gc.sh +99 -0
- package/scripts/platform/resolve-images.sh +107 -0
- package/scripts/server/{jenkins-app-apply → app-apply} +9 -10
- package/scripts/server/app-purge +1 -1
- package/scripts/server/app-remove +8 -9
- package/ansible/roles/app/templates/casc-app.yaml.j2 +0 -68
- package/compose/jenkins/Dockerfile +0 -40
- package/compose/jenkins/casc/00-platform.yaml +0 -158
- package/compose/jenkins/plugins.txt +0 -97
- package/compose/jenkins-agent/Dockerfile +0 -24
- package/scripts/server/jenkins-api +0 -28
- package/scripts/server/jenkins-reload +0 -12
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# registry-gc.sh — keep the images apps actually shipped, delete the rest.
|
|
4
|
+
#
|
|
5
|
+
# Keep set: every image referenced by the newest KEEP_RELEASES release records
|
|
6
|
+
# of each app (the deployer writes one per deploy), plus every image any
|
|
7
|
+
# container on this host uses. A registered app (its apps/<app>.json exists)
|
|
8
|
+
# with no release record yet is not touched — it has not deployed for the first time. An app
|
|
9
|
+
# that is no longer registered has all its images collected. A deleted image
|
|
10
|
+
# is not lost for good: resolve-images.sh --ensure
|
|
11
|
+
# rebuilds whatever a deploy asks for and the registry no longer has.
|
|
12
|
+
#
|
|
13
|
+
# Then: registry garbage-collect (blobs), BuildKit cache and host images older
|
|
14
|
+
# than a week. Runs nightly in the deployer (`kayra job <server> gc` by hand).
|
|
15
|
+
# =============================================================================
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
KEEP_RELEASES="${KEEP_RELEASES:-10}"
|
|
19
|
+
REGISTRY="${REGISTRY_PUSH_HOST:-registry:5000}"
|
|
20
|
+
platform_dir="${PLATFORM_DIR:?}"
|
|
21
|
+
releases="${platform_dir}/releases"
|
|
22
|
+
|
|
23
|
+
work="$(mktemp -d)"
|
|
24
|
+
trap 'rm -rf "$work"' EXIT
|
|
25
|
+
|
|
26
|
+
# ── Keep set (as repo:tag, registry host stripped) ──────────────────────────
|
|
27
|
+
for dir in "$releases"/*/; do
|
|
28
|
+
[ -d "$dir" ] || continue
|
|
29
|
+
ls -1 "$dir" | sort | tail -n "$KEEP_RELEASES" | while read -r record; do
|
|
30
|
+
cut -d= -f2- "${dir}${record}"
|
|
31
|
+
done
|
|
32
|
+
done > "$work/refs"
|
|
33
|
+
docker ps -a --format '{{.Image}}' >> "$work/refs"
|
|
34
|
+
sed -E 's#^[^/]+:[0-9]+/##' "$work/refs" | sort -u > "$work/keep"
|
|
35
|
+
ls -1 "$releases" 2>/dev/null > "$work/released" || true
|
|
36
|
+
ls -1 "${platform_dir}/apps" 2>/dev/null | sed -n 's/^\(.*\)\.json$/\1/p' > "$work/registered" || true
|
|
37
|
+
|
|
38
|
+
digest() {
|
|
39
|
+
curl -sfI \
|
|
40
|
+
-H 'Accept: application/vnd.oci.image.index.v1+json' \
|
|
41
|
+
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
|
|
42
|
+
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
|
|
43
|
+
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
|
|
44
|
+
"http://${REGISTRY}/v2/$1/manifests/$2" \
|
|
45
|
+
| tr -d '\r' | sed -n 's/^[Dd]ocker-[Cc]ontent-[Dd]igest: //p'
|
|
46
|
+
}
|
|
47
|
+
json_list() { jq -r ".$1 // [] | .[]"; }
|
|
48
|
+
|
|
49
|
+
# ── Delete untracked manifests ──────────────────────────────────────────────
|
|
50
|
+
# By digest, and never a digest a kept tag also points at: deleting a manifest
|
|
51
|
+
# removes every tag on it.
|
|
52
|
+
# Fetched into variables first: a failure inside `for x in $(...)` would not
|
|
53
|
+
# trip set -e, and a GC that cannot list anything looks like one with nothing
|
|
54
|
+
# to delete.
|
|
55
|
+
repos=$(curl -sf "http://${REGISTRY}/v2/_catalog?n=1000" | json_list repositories)
|
|
56
|
+
deleted=0
|
|
57
|
+
for repo in $repos; do
|
|
58
|
+
app="${repo%%/*}"
|
|
59
|
+
if grep -qxF "$app" "$work/registered" && ! grep -qxF "$app" "$work/released"; then
|
|
60
|
+
echo "skip ${repo} (${app} has not deployed yet)"
|
|
61
|
+
continue
|
|
62
|
+
fi
|
|
63
|
+
: > "$work/kept-digests"; : > "$work/candidates"
|
|
64
|
+
tags=$(curl -sf "http://${REGISTRY}/v2/${repo}/tags/list" | json_list tags)
|
|
65
|
+
for tag in $tags; do
|
|
66
|
+
d=$(digest "$repo" "$tag")
|
|
67
|
+
[ -n "$d" ] || continue
|
|
68
|
+
if grep -qxF "${repo}:${tag}" "$work/keep"; then
|
|
69
|
+
echo "$d" >> "$work/kept-digests"
|
|
70
|
+
else
|
|
71
|
+
echo "$tag $d" >> "$work/candidates"
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
while read -r tag d; do
|
|
75
|
+
grep -qxF "$d" "$work/kept-digests" && continue
|
|
76
|
+
curl -sf -X DELETE "http://${REGISTRY}/v2/${repo}/manifests/${d}" >/dev/null
|
|
77
|
+
echo "deleted ${repo}:${tag}"
|
|
78
|
+
deleted=$((deleted + 1))
|
|
79
|
+
done < "$work/candidates"
|
|
80
|
+
done
|
|
81
|
+
echo "Deleted ${deleted} manifest(s)."
|
|
82
|
+
|
|
83
|
+
# ── Reclaim blobs ───────────────────────────────────────────────────────────
|
|
84
|
+
# garbage-collect is only safe while nothing pushes, so the registry is stopped
|
|
85
|
+
# for the few seconds it takes; the trap brings it back whatever happens.
|
|
86
|
+
if [ "$deleted" -gt 0 ]; then
|
|
87
|
+
registry=$(docker ps -aq \
|
|
88
|
+
--filter label=com.docker.compose.project=platform \
|
|
89
|
+
--filter label=com.docker.compose.service=registry)
|
|
90
|
+
image=$(docker inspect -f '{{.Config.Image}}' "$registry")
|
|
91
|
+
docker stop "$registry" >/dev/null
|
|
92
|
+
trap 'docker start "$registry" >/dev/null; rm -rf "$work"' EXIT
|
|
93
|
+
docker run --rm --volumes-from "$registry" "$image" \
|
|
94
|
+
garbage-collect /etc/docker/registry/config.yml | tail -n 3
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ── Build cache and host images ─────────────────────────────────────────────
|
|
98
|
+
buildctl --addr tcp://buildkitd:1234 prune --keep-duration 168h | tail -n 1
|
|
99
|
+
docker image prune -af --filter until=168h | tail -n 1
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2154 # read_manifest assigns its variables via printf -v
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# resolve-images.sh <app> <app-root> --plan | --ensure <artifact>
|
|
5
|
+
#
|
|
6
|
+
# Content-addressed image plan and build for any app. Every
|
|
7
|
+
# <app-root>/**/docker/image.json is an artifact, named after the directory
|
|
8
|
+
# that holds docker/:
|
|
9
|
+
# { "image": "<app>/<name>", "dockerfile": "...", "context": "...",
|
|
10
|
+
# "inputs": ["...", ...] } all paths relative to <app-root>
|
|
11
|
+
#
|
|
12
|
+
# Two modes, because the builder has to know what it is about to do before it can
|
|
13
|
+
# show progress through it:
|
|
14
|
+
# --plan One TSV row per artifact: name, env var, image ref.
|
|
15
|
+
# Pure git — no registry, no daemon, no side effects.
|
|
16
|
+
# --ensure <name> Build and push that artifact if its tag is missing from
|
|
17
|
+
# the registry. Prints CACHED or BUILT.
|
|
18
|
+
#
|
|
19
|
+
# Tag = SHA of the last commit touching any declared input: same inputs, same
|
|
20
|
+
# content, same tag, no rebuild — on any branch.
|
|
21
|
+
#
|
|
22
|
+
# Builds go through buildkitd (BUILDKIT_HOST), never a docker daemon. Two
|
|
23
|
+
# registry names for the same storage:
|
|
24
|
+
# REGISTRY_PUSH_HOST registry:5000 reachable from containers (buildkit)
|
|
25
|
+
# REGISTRY localhost:5000 reachable from the host daemon (compose)
|
|
26
|
+
#
|
|
27
|
+
# Requires: git, jq; --ensure also curl and buildctl.
|
|
28
|
+
# =============================================================================
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
app="${1:?Usage: $0 <app> <app-root> --plan | --ensure <artifact>}"
|
|
32
|
+
root="${2:?}"
|
|
33
|
+
shift 2
|
|
34
|
+
REGISTRY="${REGISTRY:-localhost:5000}"
|
|
35
|
+
REGISTRY_PUSH_HOST="${REGISTRY_PUSH_HOST:-registry:5000}"
|
|
36
|
+
|
|
37
|
+
manifests() {
|
|
38
|
+
find "$root" -path '*/node_modules' -prune -o -path '*/docker/image.json' -print | sort
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Sets: image dockerfile context tag ref push_ref var_name
|
|
42
|
+
# (printf -v assigns them, which shellcheck cannot follow: see SC2154 above.)
|
|
43
|
+
read_manifest() {
|
|
44
|
+
local manifest="$1" field
|
|
45
|
+
for field in image dockerfile context; do
|
|
46
|
+
printf -v "$field" '%s' "$(jq -er ".${field}" "$manifest")"
|
|
47
|
+
done
|
|
48
|
+
# The registry has one namespace per app; an app may only name images in its own.
|
|
49
|
+
case "$image" in
|
|
50
|
+
"${app}/"*) ;;
|
|
51
|
+
*) echo "ERROR: ${manifest}: image ${image} is outside the ${app}/ namespace" >&2; return 1 ;;
|
|
52
|
+
esac
|
|
53
|
+
|
|
54
|
+
local inputs=() input
|
|
55
|
+
while IFS= read -r input; do inputs+=("$input"); done < <(jq -r '.inputs[]' "$manifest")
|
|
56
|
+
tag=$(git -C "$root" rev-list -1 HEAD -- "${inputs[@]}" | cut -c1-12)
|
|
57
|
+
[ -n "$tag" ] || { echo "ERROR: ${manifest}: no commit touches its inputs" >&2; return 1; }
|
|
58
|
+
|
|
59
|
+
ref="${REGISTRY}/${image}:${tag}"
|
|
60
|
+
push_ref="${REGISTRY_PUSH_HOST}/${image}:${tag}"
|
|
61
|
+
# shop/storefront -> STOREFRONT_IMAGE
|
|
62
|
+
var_name="$(basename "$image" | tr '[:lower:]-' '[:upper:]_')_IMAGE"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
artifact_name() { basename "$(dirname "$(dirname "$1")")"; }
|
|
66
|
+
|
|
67
|
+
plan() {
|
|
68
|
+
local manifest
|
|
69
|
+
for manifest in $(manifests); do
|
|
70
|
+
read_manifest "$manifest"
|
|
71
|
+
printf '%s\t%s\t%s\n' "$(artifact_name "$manifest")" "$var_name" "$ref"
|
|
72
|
+
done
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ensure() {
|
|
76
|
+
local manifest
|
|
77
|
+
# .../<artifact>/docker/image.json
|
|
78
|
+
manifest=$(manifests | awk -F/ -v name="$1" '$(NF-2) == name')
|
|
79
|
+
[ -n "$manifest" ] || { echo "ERROR: no artifact $1 under ${root}" >&2; return 1; }
|
|
80
|
+
read_manifest "$manifest"
|
|
81
|
+
|
|
82
|
+
local status
|
|
83
|
+
status=$(curl -s -o /dev/null -w '%{http_code}' \
|
|
84
|
+
-H 'Accept: application/vnd.oci.image.index.v1+json' \
|
|
85
|
+
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
|
|
86
|
+
"http://${REGISTRY_PUSH_HOST}/v2/${image}/manifests/${tag}" || echo 000)
|
|
87
|
+
if [ "$status" = "200" ]; then
|
|
88
|
+
echo "CACHED ${ref}"
|
|
89
|
+
return 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
: "${BUILDKIT_HOST:?BUILDKIT_HOST is required (e.g. tcp://buildkitd:1234)}"
|
|
93
|
+
echo "BUILDING ${image} -> ${push_ref}"
|
|
94
|
+
buildctl build \
|
|
95
|
+
--frontend dockerfile.v0 \
|
|
96
|
+
--local context="${root}/${context}" \
|
|
97
|
+
--local dockerfile="$(dirname "${root}/${dockerfile}")" \
|
|
98
|
+
--opt filename="$(basename "$dockerfile")" \
|
|
99
|
+
--output "type=image,name=${push_ref},push=true,registry.insecure=true"
|
|
100
|
+
echo "BUILT ${ref}"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "${1:-}" in
|
|
104
|
+
--plan) plan ;;
|
|
105
|
+
--ensure) ensure "${2:?--ensure needs an artifact name}" ;;
|
|
106
|
+
*) echo "Usage: $0 <app> <app-root> --plan | --ensure <artifact>" >&2; exit 2 ;;
|
|
107
|
+
esac
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# =============================================================================
|
|
3
|
-
#
|
|
3
|
+
# app-apply <app> — the one way an app's secrets reach the deployer.
|
|
4
4
|
#
|
|
5
|
-
# Reads the app's secrets as KEY=VALUE lines on stdin
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# Reads the app's secrets as KEY=VALUE lines on stdin and writes each to
|
|
6
|
+
# app-secrets/<app>/<KEY>, which the deployer reads (mounted read-only at
|
|
7
|
+
# /run/app-secrets) into the environment of that app's compose commands, and
|
|
8
|
+
# nowhere else. The next deploy uses them; nothing is reloaded.
|
|
8
9
|
#
|
|
9
10
|
# Installed by the platform role at <platform_dir>/bin. Run as root.
|
|
10
11
|
# =============================================================================
|
|
11
12
|
set -euo pipefail
|
|
12
13
|
|
|
13
|
-
app="${1:?Usage:
|
|
14
|
+
app="${1:?Usage: app-apply <app> < secrets.env}"
|
|
14
15
|
platform_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
15
|
-
secrets_dir="${platform_dir}/
|
|
16
|
+
secrets_dir="${platform_dir}/app-secrets/${app}"
|
|
16
17
|
|
|
17
18
|
# Staged next to the target and swapped in whole, so keys the app no longer
|
|
18
19
|
# declares disappear instead of lingering.
|
|
@@ -27,10 +28,8 @@ done
|
|
|
27
28
|
rm -rf "$secrets_dir"
|
|
28
29
|
mv "$staging" "$secrets_dir"
|
|
29
30
|
trap - EXIT
|
|
30
|
-
# The
|
|
31
|
+
# The deployer runs as uid 1000 and reads these through a read-only mount.
|
|
31
32
|
chown -R 1000:1000 "$secrets_dir"
|
|
32
33
|
find "$secrets_dir" -type f -exec chmod 0400 {} +
|
|
33
34
|
chmod 0500 "$secrets_dir"
|
|
34
|
-
|
|
35
|
-
"${platform_dir}/bin/jenkins-reload"
|
|
36
|
-
echo "Applied Jenkins config for ${app}."
|
|
35
|
+
echo "Applied ${app}'s secrets."
|
package/scripts/server/app-purge
CHANGED
|
@@ -12,7 +12,7 @@ set -euo pipefail
|
|
|
12
12
|
app="${1:?Usage: app-purge <app>}"
|
|
13
13
|
platform_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
14
14
|
|
|
15
|
-
if [ -e "${platform_dir}/
|
|
15
|
+
if [ -e "${platform_dir}/apps/${app}.json" ]; then
|
|
16
16
|
echo "ERROR: ${app} is still registered on this server — remove it from server.yml and run \`kayra apps <server>\` first." >&2
|
|
17
17
|
exit 1
|
|
18
18
|
fi
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
# app-remove <app> — take an app off this server, keeping its data.
|
|
4
4
|
#
|
|
5
5
|
# Stops its containers and removes everything the platform set up for it:
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# its registration, secret files, .env, runs and release records (so rollback
|
|
7
|
+
# no longer offers it). Its images are collected by the next registry GC,
|
|
8
|
+
# since the app is no longer registered.
|
|
9
9
|
#
|
|
10
10
|
# Volumes stay: dropping a line from server.yml must never silently destroy a
|
|
11
11
|
# database. `app-purge <app>` deletes them deliberately.
|
|
@@ -19,12 +19,11 @@ app="${1:?Usage: app-remove <app>}"
|
|
|
19
19
|
platform_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
20
20
|
apps_dir="$(sed -n 's/^APPS_DIR=//p' "${platform_dir}/.env")"
|
|
21
21
|
|
|
22
|
+
# Unregistered first: from here on no push, poll or rollback starts a run.
|
|
23
|
+
rm -f "${platform_dir}/apps/${app}.json"
|
|
22
24
|
docker compose -p "$app" down --remove-orphans
|
|
23
|
-
rm -rf "${platform_dir}/
|
|
24
|
-
"${platform_dir}/
|
|
25
|
+
rm -rf "${platform_dir}/app-secrets/${app}" \
|
|
26
|
+
"${platform_dir}/runs/${app}" \
|
|
27
|
+
"${platform_dir}/releases/${app}" \
|
|
25
28
|
"${apps_dir:?}/${app}"
|
|
26
|
-
# Without its fragment the reload drops the app's credentials.
|
|
27
|
-
"${platform_dir}/bin/jenkins-reload"
|
|
28
|
-
"${platform_dir}/bin/jenkins-api" POST "/job/${app}/doDelete" -o /dev/null || true
|
|
29
|
-
(cd "$platform_dir" && docker compose exec -T jenkins-controller rm -rf "/var/jenkins_home/releases/${app}")
|
|
30
29
|
echo "Removed ${app}. Its volumes are kept — app-purge ${app} deletes them."
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
{#- Rendered once per app by the platform app role. The jobs run the platform
|
|
2
|
-
library's pipelines; the app's repo is only ever checked out as data. -#}
|
|
3
|
-
{% set coords = {
|
|
4
|
-
'name': app.name, 'repo': app_repo, 'ref': app_ref, 'path': app.path | default('.'),
|
|
5
|
-
'hosts': manifest.subdomains | map('regex_replace', '$', '.' ~ domain) | list,
|
|
6
|
-
'secrets': manifest.secrets | default([]),
|
|
7
|
-
} -%}
|
|
8
|
-
{% set literal = coords | to_json | regex_replace('^\\{', '[') | regex_replace('\\}$', ']') | regex_replace('"(\\w+)": ', '\\1: ') %}
|
|
9
|
-
# =============================================================================
|
|
10
|
-
# {{ app.name }} — generated by the platform app role. Do not edit on the server.
|
|
11
|
-
# =============================================================================
|
|
12
|
-
|
|
13
|
-
credentials:
|
|
14
|
-
system:
|
|
15
|
-
domainCredentials:
|
|
16
|
-
- credentials:
|
|
17
|
-
{% for key in manifest.secrets | default([]) %}
|
|
18
|
-
- string:
|
|
19
|
-
scope: GLOBAL
|
|
20
|
-
id: "{{ app.name }}-{{ key | lower | replace('_', '-') }}"
|
|
21
|
-
description: "{{ app.name }}: {{ key }}"
|
|
22
|
-
secret: "${readFile:/run/app-secrets/{{ app.name }}/{{ key }}}"
|
|
23
|
-
{% endfor %}
|
|
24
|
-
|
|
25
|
-
jobs:
|
|
26
|
-
- script: |
|
|
27
|
-
folder("{{ app.name }}") {
|
|
28
|
-
displayName("{{ app.name }}")
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
pipelineJob("{{ app.name }}/build") {
|
|
32
|
-
displayName("Build")
|
|
33
|
-
description("Builds {{ app.name }}'s images from {{ app_ref }} on every push (GitHub webhook, hourly poll as fallback); any other branch on demand. A green {{ app_ref }} build deploys that exact commit.")
|
|
34
|
-
logRotator { numToKeep(20) }
|
|
35
|
-
// Declared here as well as in the pipeline: a JCasC reload regenerates
|
|
36
|
-
// the job from this DSL, and parameters only the pipeline declared
|
|
37
|
-
// would be gone — Jenkins then silently drops the ones passed in.
|
|
38
|
-
parameters {
|
|
39
|
-
stringParam("BRANCH", "{{ app_ref }}", "Branch, tag or commit to build")
|
|
40
|
-
}
|
|
41
|
-
definition {
|
|
42
|
-
cps {
|
|
43
|
-
script('''@Library('platform') _
|
|
44
|
-
appBuild({{ literal }})''')
|
|
45
|
-
sandbox(true)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
triggers {
|
|
49
|
-
githubPush()
|
|
50
|
-
scm("H * * * *")
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
pipelineJob("{{ app.name }}/deploy") {
|
|
55
|
-
displayName("Deploy")
|
|
56
|
-
description("Deploys any branch, tag or commit of {{ app.name }}: builds missing images, admits the compose config against the platform policy, runs it, records the release. Rollback starts it with an older SHA.")
|
|
57
|
-
logRotator { numToKeep(50) }
|
|
58
|
-
parameters {
|
|
59
|
-
stringParam("REF", "{{ app_ref }}", "Branch, tag or commit SHA to deploy")
|
|
60
|
-
}
|
|
61
|
-
definition {
|
|
62
|
-
cps {
|
|
63
|
-
script('''@Library('platform') _
|
|
64
|
-
appDeploy({{ literal }})''')
|
|
65
|
-
sandbox(true)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
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"
|
|
@@ -1,158 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
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
|