mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Merge pull request #3 from Patiun/claude/patiun-rebrand-gjoo9f
Deploy from GitHub with a systemd timer instead of Gitea Actions
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
# Gitea Actions on Cosmos: Gitea mirrors GitHub, and this builds, checks and
|
|
||||||
# restarts the GregKilmer.com stack on each new commit to master.
|
|
||||||
# It runs on a host-mode runner on Cosmos labelled "cosmos" (docs/deploy.md).
|
|
||||||
name: Deploy GregKilmer.com
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
# A pull mirror's sync may not raise a push event; this catches new commits
|
|
||||||
# anyway. deploy.sh does nothing when the commit is already running.
|
|
||||||
schedule:
|
|
||||||
- cron: "*/15 * * * *"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: gregkilmer-deploy
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: cosmos
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: ./deploy/deploy.sh
|
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
# docker build --target admin . the admin page (tailnet only)
|
# docker build --target admin . the admin page (tailnet only)
|
||||||
# docker build --target site . the public site
|
# docker build --target site . the public site
|
||||||
#
|
#
|
||||||
# The Gitea Actions workflow builds check first, so a red check stops a deploy.
|
# deploy/deploy.sh builds check first, so a red check stops a deploy.
|
||||||
# See docs/deploy.md.
|
# See docs/deploy.md.
|
||||||
|
|
||||||
# Pinned by version (Node 22.20, released 2025-09). Override with --build-arg to
|
# Pinned by version (Node 22.20, released 2025-09). Override with --build-arg to
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ with the decisions behind it, is the
|
|||||||
| `services/admin` | The admin page: the review queue with diffs, history and rollback, quick capture, store orders, draft previews and sync health. A client of the content service, on the tailnet only. See [docs/admin.md](docs/admin.md). |
|
| `services/admin` | The admin page: the review queue with diffs, history and rollback, quick capture, store orders, draft previews and sync health. A client of the content service, on the tailnet only. See [docs/admin.md](docs/admin.md). |
|
||||||
| `packages/ui` | The component library: the approved element rules and ornament kit as Astro components, with the brand fonts, seals and icons, and a gallery page that shows them all. See [docs/components.md](docs/components.md). |
|
| `packages/ui` | The component library: the approved element rules and ornament kit as Astro components, with the brand fonts, seals and icons, and a gallery page that shows them all. See [docs/components.md](docs/components.md). |
|
||||||
| `services/site` | The public site: every page rendered from the content service's published notes, the resume PDF, and the inquiry and order forms. See [docs/site.md](docs/site.md). |
|
| `services/site` | The public site: every page rendered from the content service's published notes, the resume PDF, and the inquiry and order forms. See [docs/site.md](docs/site.md). |
|
||||||
| `deploy`, `Dockerfile`, `.gitea` | The Cosmos stack: images, compose file, deploy and rollback scripts, and the Gitea Actions workflow. See [docs/deploy.md](docs/deploy.md). |
|
| `deploy`, `Dockerfile` | The Cosmos stack: images, compose file, deploy and rollback scripts, and the systemd timer that deploys each new commit to master. See [docs/deploy.md](docs/deploy.md). |
|
||||||
| `tools/migrate` | The one-time migration from the old site's data into Site folder notes. |
|
| `tools/migrate` | The one-time migration from the old site's data into Site folder notes. |
|
||||||
| `docs` | How the pieces fit. Start with [docs/content-model.md](docs/content-model.md). |
|
| `docs` | How the pieces fit. Start with [docs/content-model.md](docs/content-model.md). |
|
||||||
| `legacy` | The old site, kept for reference only. Nothing outside it imports from it except the migration. |
|
| `legacy` | The old site, kept for reference only. Nothing outside it imports from it except the migration. |
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# The GregKilmer.com stack on Cosmos: content service, admin page and public site.
|
# The GregKilmer.com stack on Cosmos: content service, admin page and public site.
|
||||||
#
|
#
|
||||||
# Lives at /home/greg/homelab/gregkilmer/compose.yaml (the Gitea Actions deploy
|
# Lives at /home/greg/homelab/gregkilmer/compose.yaml (deploy/deploy.sh
|
||||||
# copies it there from deploy/compose.yaml on every build). Settings:
|
# copies it there from deploy/compose.yaml on every build). Settings:
|
||||||
# .env stack settings: GK_TAG, VAULT_SITE, PUID, PGID (deploy/stack.env.example)
|
# .env stack settings: GK_TAG, VAULT_SITE, PUID, PGID (deploy/stack.env.example)
|
||||||
# content.env content service secrets (services/content/.env.example)
|
# content.env content service secrets (services/content/.env.example)
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# Builds the three images for the checked-out commit, runs the checks, and
|
# Builds the three images for the checked-out commit, runs the checks, and
|
||||||
# restarts the stack on it. If the new containers don't come up healthy, the
|
# restarts the stack on it. If the new containers don't come up healthy, the
|
||||||
# stack goes back to the last good tag. Run from the repo root on Cosmos, by the
|
# stack goes back to the last good tag. Run from the repo root on Cosmos, by the
|
||||||
# Gitea Actions workflow or by hand. See docs/deploy.md.
|
# deploy timer (pull-and-deploy.sh) or by hand. See docs/deploy.md.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
STACK="${GK_STACK_DIR:-/home/greg/homelab/gregkilmer}"
|
STACK="${GK_STACK_DIR:-/home/greg/homelab/gregkilmer}"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Deploys GregKilmer.com from the GitHub clone when master moves.
|
||||||
|
# Install: sudo cp deploy/gk-deploy.{service,timer} /etc/systemd/system/
|
||||||
|
# sudo systemctl daemon-reload && sudo systemctl enable --now gk-deploy.timer
|
||||||
|
# Logs: journalctl -u gk-deploy. See docs/deploy.md.
|
||||||
|
[Unit]
|
||||||
|
Description=Deploy GregKilmer.com from GitHub
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=greg
|
||||||
|
Environment=GK_STACK_DIR=/home/greg/homelab/gregkilmer
|
||||||
|
ExecStart=/home/greg/homelab/gregkilmer/repo/deploy/pull-and-deploy.sh
|
||||||
|
TimeoutStartSec=30min
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Check GitHub for a new GregKilmer.com commit every 5 minutes
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitInactiveSec=5min
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run by gk-deploy.timer on Cosmos: fetches master from GitHub into the
|
||||||
|
# deploy-only clone and, when it has moved, runs deploy.sh on it. A commit that
|
||||||
|
# failed to deploy is not retried until master moves again (or FORCE=1).
|
||||||
|
# Pings DEPLOY_HEALTHCHECK_URL from the stack's .env when set. See docs/deploy.md.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
STACK="${GK_STACK_DIR:-/home/greg/homelab/gregkilmer}"
|
||||||
|
export GK_STACK_DIR="$STACK"
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
hc="$(grep -E '^DEPLOY_HEALTHCHECK_URL=' "$STACK/.env" 2>/dev/null | cut -d= -f2- || true)"
|
||||||
|
ping() { [ -n "$hc" ] && curl -fsS -m 10 --retry 3 "$hc$1" >/dev/null 2>&1 || true; }
|
||||||
|
|
||||||
|
if ! git fetch --quiet origin master; then
|
||||||
|
echo "Could not fetch from GitHub"
|
||||||
|
ping /fail
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
target="$(git rev-parse --short=12 origin/master)"
|
||||||
|
running="$(grep -E '^GK_TAG=' "$STACK/.env" 2>/dev/null | cut -d= -f2- || true)"
|
||||||
|
failed="$(cat "$STACK/.failed-tag" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ "$target" = "$running" ] && [ "${FORCE:-}" != "1" ]; then
|
||||||
|
ping ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ "$target" = "$failed" ] && [ "${FORCE:-}" != "1" ]; then
|
||||||
|
echo "$target failed to deploy before; waiting for a new commit (FORCE=1 to retry)"
|
||||||
|
ping /fail
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The clone is for deploys only, so it always matches master exactly.
|
||||||
|
git reset --quiet --hard origin/master
|
||||||
|
git clean -fdq
|
||||||
|
|
||||||
|
ping /start
|
||||||
|
if ./deploy/deploy.sh; then
|
||||||
|
rm -f "$STACK/.failed-tag"
|
||||||
|
ping ""
|
||||||
|
else
|
||||||
|
echo "$target" > "$STACK/.failed-tag"
|
||||||
|
ping /fail
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Stack settings for compose.yaml. On Cosmos: /home/greg/homelab/gregkilmer/.env
|
# Stack settings for compose.yaml. On Cosmos: /home/greg/homelab/gregkilmer/.env
|
||||||
# The Gitea Actions deploy rewrites GK_TAG on every build; GK_PREVIOUS_TAG is the rollback.
|
# deploy.sh rewrites GK_TAG on every deploy; GK_PREVIOUS_TAG is the rollback.
|
||||||
GK_TAG=
|
GK_TAG=
|
||||||
GK_PREVIOUS_TAG=
|
GK_PREVIOUS_TAG=
|
||||||
# The vault's Site folder on the host.
|
# The vault's Site folder on the host.
|
||||||
@@ -7,3 +7,6 @@ VAULT_SITE=/home/greg/homelab/obsidian-vault/20-29_Makerspace/24_Homelab_Infrast
|
|||||||
# The user the containers run as: the owner of the vault files (id -u greg, id -g greg).
|
# The user the containers run as: the owner of the vault files (id -u greg, id -g greg).
|
||||||
PUID=1000
|
PUID=1000
|
||||||
PGID=1000
|
PGID=1000
|
||||||
|
# Healthchecks ping for the deploy timer (5-minute period): red when GitHub
|
||||||
|
# can't be reached or a commit fails to deploy.
|
||||||
|
DEPLOY_HEALTHCHECK_URL=
|
||||||
|
|||||||
+40
-19
@@ -16,7 +16,7 @@ Files in the repo:
|
|||||||
- `deploy/compose.yaml`: the stack. Containers run as the vault's owner, with a read-only root filesystem, no capabilities and `no-new-privileges`.
|
- `deploy/compose.yaml`: the stack. Containers run as the vault's owner, with a read-only root filesystem, no capabilities and `no-new-privileges`.
|
||||||
- `deploy/deploy.sh`: builds `check`, then the three images tagged with the commit, and restarts the stack. If the new containers aren't healthy within two minutes, it puts the last good tag back. Run it again on the same commit and it does nothing (`FORCE=1` rebuilds).
|
- `deploy/deploy.sh`: builds `check`, then the three images tagged with the commit, and restarts the stack. If the new containers aren't healthy within two minutes, it puts the last good tag back. Run it again on the same commit and it does nothing (`FORCE=1` rebuilds).
|
||||||
- `deploy/rollback.sh`: switches to the previous tag. Run it again to roll forward.
|
- `deploy/rollback.sh`: switches to the previous tag. Run it again to roll forward.
|
||||||
- `.gitea/workflows/deploy.yml`: runs `deploy.sh` on Cosmos for each new commit to master.
|
- `deploy/pull-and-deploy.sh`, with `deploy/gk-deploy.service` and `gk-deploy.timer`: every 5 minutes, Cosmos fetches master from GitHub into its own clone of the repo and runs `deploy.sh` when master has moved. A commit that failed to deploy isn't retried until master moves again.
|
||||||
|
|
||||||
## On Cosmos
|
## On Cosmos
|
||||||
|
|
||||||
@@ -24,8 +24,9 @@ Everything lives in `/home/greg/homelab/gregkilmer`:
|
|||||||
|
|
||||||
```
|
```
|
||||||
/home/greg/homelab/gregkilmer/
|
/home/greg/homelab/gregkilmer/
|
||||||
|
repo/ clone of github.com/Patiun/com.gregkilmer.www, for deploys only
|
||||||
compose.yaml copied from deploy/compose.yaml by every deploy
|
compose.yaml copied from deploy/compose.yaml by every deploy
|
||||||
.env GK_TAG, GK_PREVIOUS_TAG, VAULT_SITE, PUID, PGID
|
.env GK_TAG, GK_PREVIOUS_TAG, VAULT_SITE, PUID, PGID, DEPLOY_HEALTHCHECK_URL
|
||||||
content.env content service secrets and sync settings
|
content.env content service secrets and sync settings
|
||||||
admin.env ADMIN_TOKEN
|
admin.env ADMIN_TOKEN
|
||||||
site.env site secrets: tokens, Turnstile, SMTP
|
site.env site secrets: tokens, Turnstile, SMTP
|
||||||
@@ -36,15 +37,24 @@ Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the en
|
|||||||
|
|
||||||
## First-time setup
|
## First-time setup
|
||||||
|
|
||||||
1. **Folder and settings.** Run these from a checkout of the repo:
|
1. **Clone and settings.** The repo is private, so give Cosmos read-only access with a deploy key:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh-keygen -t ed25519 -N "" -C cosmos-gregkilmer-deploy -f ~/.ssh/gregkilmer_deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `~/.ssh/gregkilmer_deploy.pub` on GitHub under the repo's Settings, then Deploy keys, with write access left off. Then:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -p /home/greg/homelab/gregkilmer/data/content
|
mkdir -p /home/greg/homelab/gregkilmer/data/content
|
||||||
cd /home/greg/homelab/gregkilmer
|
cd /home/greg/homelab/gregkilmer
|
||||||
cp <repo>/deploy/stack.env.example .env
|
GIT_SSH_COMMAND="ssh -i ~/.ssh/gregkilmer_deploy -o IdentitiesOnly=yes" \
|
||||||
cp <repo>/services/content/.env.example content.env
|
git clone git@github.com:Patiun/com.gregkilmer.www.git repo
|
||||||
cp <repo>/deploy/admin.env.example admin.env
|
git -C repo config core.sshCommand "ssh -i ~/.ssh/gregkilmer_deploy -o IdentitiesOnly=yes"
|
||||||
cp <repo>/services/site/.env.example site.env
|
cp repo/deploy/stack.env.example .env
|
||||||
|
cp repo/services/content/.env.example content.env
|
||||||
|
cp repo/deploy/admin.env.example admin.env
|
||||||
|
cp repo/services/site/.env.example site.env
|
||||||
chmod 600 .env *.env
|
chmod 600 .env *.env
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,18 +70,28 @@ Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the en
|
|||||||
|
|
||||||
3. **The rest of the settings.** Fill in Healthchecks, the Discord webhook, Turnstile, SMTP and the sync logins. The list is in `/mnt/project-files/website/phase6/setup-on-cosmos.md` and in each `.env.example`.
|
3. **The rest of the settings.** Fill in Healthchecks, the Discord webhook, Turnstile, SMTP and the sync logins. The list is in `/mnt/project-files/website/phase6/setup-on-cosmos.md` and in each `.env.example`.
|
||||||
|
|
||||||
4. **Gitea runner.** The workflow needs an `act_runner` on Cosmos with the label `cosmos` running in host mode (`cosmos:host`). It runs as a user in the `docker` group with write access to `/home/greg/homelab/gregkilmer`.
|
4. **First deploy.** Run it by hand once and watch it:
|
||||||
- A runner with the Docker socket is root-equivalent. Register it for this repository only, not for the whole instance.
|
|
||||||
|
|
||||||
5. **Gitea mirror.** Mirror `github.com/Patiun/com.gregkilmer.www` into Gitea as a pull mirror and turn Actions on for it.
|
```sh
|
||||||
- A pull mirror's sync may not raise a push event. The workflow therefore also runs every 15 minutes and deploys only when master has moved.
|
/home/greg/homelab/gregkilmer/repo/deploy/pull-and-deploy.sh
|
||||||
- Check once that a new commit reaches Cosmos. If the schedule doesn't fire on a mirror either, run the workflow by hand from the Actions tab.
|
```
|
||||||
|
|
||||||
6. **First deploy.** Run it from the Actions tab, or by hand from a checkout: `./deploy/deploy.sh`. Then check:
|
Then check:
|
||||||
- `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml ps` shows three healthy containers.
|
- `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml ps` shows three healthy containers.
|
||||||
- `docker logs gk-content` says `Content service on 0.0.0.0:4310: N notes`.
|
- `docker logs gk-content` says `Content service on 0.0.0.0:4310: N notes`.
|
||||||
|
|
||||||
7. **Nginx Proxy Manager.** Add a proxy host for each hostname. Force SSL on each. For `content`, add `proxy_buffering off;` under Advanced, because MCP streams its responses.
|
5. **Deploy timer.** This makes every later merge to master deploy on its own:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo cp /home/greg/homelab/gregkilmer/repo/deploy/gk-deploy.{service,timer} /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload && sudo systemctl enable --now gk-deploy.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
- The service runs as `greg`, who must be in the `docker` group.
|
||||||
|
- Make a Healthchecks check with a 5-minute period, and set its URL as `DEPLOY_HEALTHCHECK_URL` in `.env`. It turns red when GitHub can't be reached or a commit fails to deploy.
|
||||||
|
- Logs: `journalctl -u gk-deploy`.
|
||||||
|
|
||||||
|
6. **Nginx Proxy Manager.** Add a proxy host for each hostname. Force SSL on each. For `content`, add `proxy_buffering off;` under Advanced, because MCP streams its responses.
|
||||||
|
|
||||||
| Host | Forward to | Access list |
|
| Host | Forward to | Access list |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
@@ -83,13 +103,13 @@ Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the en
|
|||||||
- Certificates: Let's Encrypt can't reach a tailnet-only host over HTTP, so use NPM's DNS challenge with your DNS provider. This is the same open item as the `*.patiun.com` certificate.
|
- Certificates: Let's Encrypt can't reach a tailnet-only host over HTTP, so use NPM's DNS challenge with your DNS provider. This is the same open item as the `*.patiun.com` certificate.
|
||||||
- Agents already on homelab_network can skip NPM and use `http://gk-content:4310/mcp`.
|
- Agents already on homelab_network can skip NPM and use `http://gk-content:4310/mcp`.
|
||||||
|
|
||||||
8. **Turnstile.** Add `beta.gregkilmer.com` and `gregkilmer.com` to the widget's hostnames.
|
7. **Turnstile.** Add `beta.gregkilmer.com` and `gregkilmer.com` to the widget's hostnames.
|
||||||
|
|
||||||
9. **Healthchecks.** Create these checks:
|
8. **Healthchecks.** Create these checks:
|
||||||
- one for the content service's full scan (`HEALTHCHECK_URL`), every 5 minutes;
|
- one for the content service's full scan (`HEALTHCHECK_URL`), every 5 minutes;
|
||||||
- one per sync job you turn on (Archidekt, Suwayomi, Bambuddy and Homebox hourly; BGG and Kavita daily; covers every 15 minutes).
|
- one per sync job you turn on (Archidekt, Suwayomi, Bambuddy and Homebox hourly; BGG and Kavita daily; covers every 15 minutes).
|
||||||
|
|
||||||
10. **Inventory note.** Copy `vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md` into the vault's `24.01_PersonalWebsite` folder, so `cosmos-inventory` stops listing the containers as undocumented.
|
9. **Inventory note.** Copy `vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md` into the vault's `24.01_PersonalWebsite` folder, so `cosmos-inventory` stops listing the containers as undocumented.
|
||||||
|
|
||||||
## Backups
|
## Backups
|
||||||
|
|
||||||
@@ -108,7 +128,8 @@ Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the en
|
|||||||
|
|
||||||
## Day to day
|
## Day to day
|
||||||
|
|
||||||
- **Deploy:** merge to master on GitHub; Gitea mirrors it and the workflow deploys it.
|
- **Deploy:** merge to master on GitHub. Cosmos picks it up within 5 minutes. To deploy straight away, run `sudo systemctl start gk-deploy`.
|
||||||
- **Roll back:** `GK_STACK_DIR=/home/greg/homelab/gregkilmer ./deploy/rollback.sh` from a checkout, or set `GK_TAG` in `.env` to any image tag still on the host and run `docker compose up -d`.
|
- **Retry a failed commit:** `FORCE=1 /home/greg/homelab/gregkilmer/repo/deploy/pull-and-deploy.sh`.
|
||||||
|
- **Roll back:** `/home/greg/homelab/gregkilmer/repo/deploy/rollback.sh`, or set `GK_TAG` in `.env` to any image tag still on the host and run `docker compose up -d`.
|
||||||
- **Logs:** `docker logs gk-content`. Sync runs also appear on the admin page's Overview.
|
- **Logs:** `docker logs gk-content`. Sync runs also appear on the admin page's Overview.
|
||||||
- **Restart one service:** `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml restart gk-site`.
|
- **Restart one service:** `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml restart gk-site`.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ tags: [homelab, service]
|
|||||||
|
|
||||||
# GregKilmer.com stack
|
# GregKilmer.com stack
|
||||||
|
|
||||||
The new GregKilmer.com: a content service that reads this folder's `Site/` notes, a tailnet-only admin page, and the public site. Repo: `github.com/Patiun/com.gregkilmer.www` (mirrored to Gitea, deployed by Gitea Actions). Runbook: `docs/deploy.md` in the repo.
|
The new GregKilmer.com: a content service that reads this folder's `Site/` notes, a tailnet-only admin page, and the public site. Repo: `github.com/Patiun/com.gregkilmer.www`, cloned to `/home/greg/homelab/gregkilmer/repo` and deployed by the `gk-deploy.timer` systemd timer when master moves. Runbook: `docs/deploy.md` in the repo.
|
||||||
|
|
||||||
## Containers
|
## Containers
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user