diff --git a/flake-parts/action-checkout/default.nix b/flake-parts/action-checkout/default.nix new file mode 100644 index 0000000..6b7c397 --- /dev/null +++ b/flake-parts/action-checkout/default.nix @@ -0,0 +1,24 @@ +{ + perSystem = + { config + , pkgs + , ... + }: + let + name = builtins.baseNameOf ./.; + script = config.writers.writePureShellScriptBin + name + [ + pkgs.bash + pkgs.coreutils + pkgs.git + pkgs.openssh + ] + '' + bash ${./script.sh} + ''; + in + { + packages.${name} = script; + }; +} diff --git a/flake-parts/action-checkout/script.sh b/flake-parts/action-checkout/script.sh new file mode 100644 index 0000000..a901eda --- /dev/null +++ b/flake-parts/action-checkout/script.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +# load BRANCH variable with default +BRANCH=${BRANCH:-main} + +# load REPO_DIR variable with default +export REPO_DIR=${REPO_DIR:-.} + +git clone --depth 1 --branch $BRANCH $REPO $REPO_DIR diff --git a/flake-parts/action-create-pr/default.nix b/flake-parts/action-create-pr/default.nix new file mode 100644 index 0000000..622ee2c --- /dev/null +++ b/flake-parts/action-create-pr/default.nix @@ -0,0 +1,25 @@ +{ + perSystem = + { config + , pkgs + , ... + }: + let + name = builtins.baseNameOf ./.; + script = config.writers.writePureShellScriptBin + name + [ + pkgs.bash + pkgs.coreutils + pkgs.git + pkgs.tea + pkgs.openssh + ] + '' + bash ${./script.sh} "$@" + ''; + in + { + packages.${name} = script; + }; +} diff --git a/flake-parts/action-create-pr/script.sh b/flake-parts/action-create-pr/script.sh new file mode 100644 index 0000000..488bfac --- /dev/null +++ b/flake-parts/action-create-pr/script.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +REMOTE_BRANCH="${REMOTE_BRANCH:-auto-pr}" +PR_TITLE="${PR_TITLE:-'This PR was created automatically'}" + +git diff --quiet || { + echo -e "\e[31mWorking tree is dirty, please commit first\e[0m" + git status + exit 1 +} + +git push origin "HEAD:$REMOTE_BRANCH" + +tea pr create "$@" \ + --head "$REMOTE_BRANCH" \ + --title "$PR_TITLE" diff --git a/flake-parts/action-ensure-tea-login/default.nix b/flake-parts/action-ensure-tea-login/default.nix new file mode 100644 index 0000000..d137837 --- /dev/null +++ b/flake-parts/action-ensure-tea-login/default.nix @@ -0,0 +1,23 @@ +{ + perSystem = + { config + , pkgs + , ... + }: + let + name = builtins.baseNameOf ./.; + script = config.writers.writePureShellScriptBin + name + [ + pkgs.bash + pkgs.coreutils + pkgs.tea + ] + '' + bash ${./script.sh} + ''; + in + { + packages.${name} = script; + }; +} diff --git a/flake-parts/action-ensure-tea-login/script.sh b/flake-parts/action-ensure-tea-login/script.sh new file mode 100644 index 0000000..4185f5d --- /dev/null +++ b/flake-parts/action-ensure-tea-login/script.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$(tea login list -o simple | wc -l)" -gt 0 ]; then + exit 0 +fi + +GITEA_TOKEN="${GITEA_TOKEN:-"$(cat "$GITEA_TOKEN_FILE")"}" + +tea login add \ + --token $GITEA_TOKEN \ + --url $GITEA_URL diff --git a/flake-parts/action-flake-update/default.nix b/flake-parts/action-flake-update/default.nix new file mode 100644 index 0000000..3b18ba3 --- /dev/null +++ b/flake-parts/action-flake-update/default.nix @@ -0,0 +1,24 @@ +{ + perSystem = + { config + , pkgs + , ... + }: + let + name = builtins.baseNameOf ./.; + script = config.writers.writePureShellScriptBin + name + [ + pkgs.bash + pkgs.coreutils + pkgs.git + pkgs.nix + ] + '' + bash ${./script.sh} + ''; + in + { + packages.${name} = script; + }; +} diff --git a/flake-parts/action-flake-update/script.sh b/flake-parts/action-flake-update/script.sh new file mode 100644 index 0000000..01666de --- /dev/null +++ b/flake-parts/action-flake-update/script.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +COMMIT_MSG="update flake lock - $(date --iso-8601=minutes)" + +nix --experimental-features "nix-command flakes" \ + flake update --commit-lock-file --commit-lockfile-summary "$COMMIT_MSG" diff --git a/flake-parts/job-flake-update/default.nix b/flake-parts/job-flake-update/default.nix new file mode 100644 index 0000000..4e0717e --- /dev/null +++ b/flake-parts/job-flake-update/default.nix @@ -0,0 +1,27 @@ +{ + perSystem = + { config + , pkgs + , self' + , ... + }: + let + name = builtins.baseNameOf ./.; + script = config.writers.writePureShellScriptBin + name + [ + pkgs.bash + pkgs.coreutils + self'.packages.action-checkout + self'.packages.action-ensure-tea-login + self'.packages.action-create-pr + self'.packages.action-flake-update + ] + '' + bash ${./script.sh} + ''; + in + { + packages.${name} = script; + }; +} diff --git a/flake-parts/job-flake-update/script.sh b/flake-parts/job-flake-update/script.sh new file mode 100644 index 0000000..bc4a570 --- /dev/null +++ b/flake-parts/job-flake-update/script.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +# prevent these variables from being unset by writePureShellScript +export KEEP_VARS="GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GITEA_URL GITEA_USER PR_TITLE REMOTE_BRANCH REPO REPO_DIR${KEEP_VARS:+ $KEEP_VARS}" + +# configure variables for actions +export PR_TITLE="Automatic flake update - $(date --iso-8601=minutes)" +export REMOTE_BRANCH="flake-update-$(date --iso-8601)" +export REPO="gitea@git.clan.lol:clan/clan-infra.git" +export REPO_DIR=$TMPDIR/repo +export GIT_AUTHOR_NAME="Clan Merge Bot" +export GIT_AUTHOR_EMAIL="clan-bot@git.clan.lol" +export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" +export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_NAME" +export GITEA_USER="clan-bot" +export GITEA_URL="https://git.clan.lol" + +action-checkout +cd $REPO_DIR +action-ensure-tea-login +action-flake-update +action-create-pr --assignees clan-bot diff --git a/flake.lock b/flake.lock index 4207609..338bb05 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,32 @@ { "nodes": { + "clan-core": { + "inputs": { + "flake-parts": [ + "flake-parts" + ], + "nixos-generators": "nixos-generators", + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": [ + "treefmt-nix" + ] + }, + "locked": { + "lastModified": 1690388700, + "narHash": "sha256-9hYtUcPe6R/Bp8S+jXw2pFfBYhiLPtAtyG3gAh1HLHo=", + "ref": "refs/heads/main", + "rev": "1a0cdf0d1d7519073db76a73934912e8d7c0c5fe", + "revCount": 108, + "type": "git", + "url": "https://git.clan.lol/clan/clan-core" + }, + "original": { + "type": "git", + "url": "https://git.clan.lol/clan/clan-core" + } + }, "disko": { "inputs": { "nixpkgs": [ @@ -40,6 +67,43 @@ "type": "github" } }, + "nixlib": { + "locked": { + "lastModified": 1688259758, + "narHash": "sha256-CYVbYQfIm3vwciCf6CCYE+WOOLE3vcfxfEfNHIfKUJQ=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "a92befce80a487380ea5e92ae515fe33cebd3ac6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixos-generators": { + "inputs": { + "nixlib": "nixlib", + "nixpkgs": [ + "clan-core", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688738567, + "narHash": "sha256-yax5BYOfpE0+95kyJmEcfKEdZBaFvCENDogBB4VQB3Q=", + "owner": "nix-community", + "repo": "nixos-generators", + "rev": "9191c85aab6b1a7ad395c13d340f2aa0e3ddf552", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-generators", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1689846894, @@ -58,6 +122,7 @@ }, "root": { "inputs": { + "clan-core": "clan-core", "disko": "disko", "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", diff --git a/flake.nix b/flake.nix index d6a92fb..eb38898 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,11 @@ srvos.url = "github:numtide/srvos"; # Use the version of nixpkgs that has been tested to work with SrvOS srvos.inputs.nixpkgs.follows = "nixpkgs"; + + clan-core.url = "git+https://git.clan.lol/clan/clan-core"; + clan-core.inputs.flake-parts.follows = "flake-parts"; + clan-core.inputs.nixpkgs.follows = "nixpkgs"; + clan-core.inputs.treefmt-nix.follows = "treefmt-nix"; }; outputs = inputs@{ flake-parts, ... }: @@ -36,6 +41,12 @@ ]; imports = [ inputs.treefmt-nix.flakeModule + inputs.clan-core.modules.flake-parts.writers + ./flake-parts/action-checkout + ./flake-parts/action-create-pr + ./flake-parts/action-ensure-tea-login + ./flake-parts/action-flake-update + ./flake-parts/job-flake-update ./targets/flake-module.nix ./modules/flake-module.nix ./pkgs/flake-module.nix diff --git a/modules/web01/clan-merge.nix b/modules/web01/clan-merge.nix index 0d63b69..9c17c7a 100644 --- a/modules/web01/clan-merge.nix +++ b/modules/web01/clan-merge.nix @@ -16,7 +16,7 @@ script = '' while sleep 10; do ${self.packages.${pkgs.system}.clan-merge}/bin/clan-merge \ - --allowed-users DavHau lassulus Mic92 \ + --allowed-users clan-bot DavHau lassulus Mic92 \ --repos clan-infra clan-core clan-homepage \ --bot-name clan-bot done diff --git a/modules/web01/default.nix b/modules/web01/default.nix index ff34edb..85bbb13 100644 --- a/modules/web01/default.nix +++ b/modules/web01/default.nix @@ -7,6 +7,7 @@ ./harmonia.nix ./homepage.nix ./postfix.nix + ./job-flake-update.nix ../zerotier ../zerotier/ctrl.nix ]; diff --git a/modules/web01/job-flake-update.nix b/modules/web01/job-flake-update.nix new file mode 100644 index 0000000..3483385 --- /dev/null +++ b/modules/web01/job-flake-update.nix @@ -0,0 +1,48 @@ +{ config, self, pkgs, ... }: { + + sops.secrets.clan-bot-gitea-token = { }; + sops.secrets.clan-bot-ssh-key = { }; + + systemd.timers.job-flake-update = { + description = "Time for flake update workflow"; + partOf = [ "job-flake-update.service" ]; + wantedBy = [ "timers.target" ]; + timerConfig = { + Persistent = true; + OnCalendar = "daily"; + }; + after = [ "network-online.target" ]; + }; + + # service to for automatic merge bot + systemd.services.job-flake-update = { + description = "Automatically update flake inputs for clan-repos"; + after = [ "network-online.target" ]; + environment = { + # secrets + GITEA_TOKEN_FILE = "%d/GITEA_TOKEN_FILE"; + CLAN_BOT_SSH_KEY_FILE = "%d/CLAN_BOT_SSH_KEY_FILE"; + + HOME = "/run/job-flake-update"; + + # used by action-checkout + REPO_DIR = "/run/job-flake-update/repo"; + + # used by git + GIT_SSH_COMMAND = "ssh -i %d/CLAN_BOT_SSH_KEY_FILE -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"; + + # prevent these variables from being unset by writePureShellScript + KEEP_VARS = "GIT_SSH_COMMAND GITEA_TOKEN_FILE"; + }; + serviceConfig = { + LoadCredential = [ + "GITEA_TOKEN_FILE:${config.sops.secrets.clan-bot-gitea-token.path}" + "CLAN_BOT_SSH_KEY_FILE:${config.sops.secrets.clan-bot-ssh-key.path}" + ]; + DynamicUser = true; + RuntimeDirectory = "job-flake-update"; + WorkingDirectory = "/run/job-flake-update"; + ExecStart = "${self.packages.${pkgs.system}.job-flake-update}/bin/job-flake-update"; + }; + }; +} diff --git a/pkgs/clan-merge/clan_merge/__init__.py b/pkgs/clan-merge/clan_merge/__init__.py index 9216132..62c6d5f 100644 --- a/pkgs/clan-merge/clan_merge/__init__.py +++ b/pkgs/clan-merge/clan_merge/__init__.py @@ -120,7 +120,9 @@ def clan_merge( f"Would merge PR {pr['number']} in repo {repo} from user {pr['user']['login']}" ) else: - print("Merging PR " + str(pr["id"])) + print( + f"Merging PR {pr['number']} in repo {repo} from user {pr['user']['login']}" + ) data = dict( Do="merge", ) diff --git a/targets/web01/secrets.yaml b/targets/web01/secrets.yaml index 76e385c..898f161 100644 --- a/targets/web01/secrets.yaml +++ b/targets/web01/secrets.yaml @@ -12,6 +12,8 @@ matrix-server-key: ENC[AES256_GCM,data:0148ezOFk8jX5KPQPCG0jQK9ajSfe/iOdUqlvys5/ registration-secret: ENC[AES256_GCM,data:EvPearZAxxb2irZFYgvy/tFA72h+IABuzwCbvy94IYR0eoHjuYw6GBde8CNUWG4SUiwyXJr4v438o/YThDhehsZ/cZFjg2o=,iv:ogN4/Iia5Zl95a3HP1KZoy86K8LyBFYw50cZUpkDNQo=,tag:5wU2OrNi7b5gWPfFZcGLjg==,type:str] gitea-actions-runner: ENC[AES256_GCM,data:JKXAa7J1V3GH8lp3UtHTBmiezJlqxX1ItHLE7UcaIeNFQH8We2imaOMVftMpVCeXTpRX,iv:W9+4wH4asw3+w28i5om0OcJFHrABC85bhjhbgGWEs8E=,tag:Rf9XBeiEoJ1Pt8Z1TDIyJA==,type:str] merge-bot-gitea-token: ENC[AES256_GCM,data:ULHcaNSYJwMVeeEq4bSiRcVRuUkE9fFUV0AkWW1wM0yHQtD+dmo1GcQ=,iv:dujDWGZ+seoVN8Eez1w3tUuMpGeOHtNLMaa+f2hOpAo=,tag:WoDTsZegC6rrbh7ygWSk+A==,type:str] +clan-bot-gitea-token: ENC[AES256_GCM,data:J+8AuAT50Xh4lKUWmigZQ/QBfNuaNKJDVuPj6jAOx06XZDwLEFtE8R8=,iv:8OGDcHbGfv6SOxe6+UBU7rTNgzYJYNJtUysSLao6H50=,tag:LxzSogjPBlxIrPcsgRU2Zw==,type:str] +clan-bot-ssh-key: ENC[AES256_GCM,data:mQAzPbzFt/FIIEo5ThXINN2FXsRMrBs/+1x/p0jDbNGLYt2LdMuYXQFevlYAK59fObNn/U6y/dheqKjtkX6BdBTLJgUKXktsJSeudsZPZ1OMtvzL0xxgx5+8Q0R+Er7BZ4ZVMpc9rolNtUojU/9gWMCALcVXz7FqGTtAYe8SEWCYinO7oP4JmIjWYnJBfPKXwq79uRp3y6dUnRLOL1Q7hCo7qFttCSssTF6HT+NJBVeAQvQ5wnWlfu/T0b7n4HB3GKfkei8Sh1ydW9k3kjFA34balXfEp3C/HHmzZO2/RWdFf45NN7itVIZhw04nrmFIW6mXaqhgJk/us9aqWFeUTTPH+nIDQfRWzcDddxrb8ZJC+/znLzX06wP18fmCHfIffyYPKcMrNxS2BKFZabybombbTYGnRV0a4881bvQLD+ScOYavYpEUTDnOg7SRQCuyDm2TtRQ5+wSUQCosYg7vTKr3btM5sV8PgDti4ou0t7YSt9bbnXv0s8/jRT+x7X7gWtw1MspJ1dWhH+FpyoPwDC4fAqjfTdcCkBFc,iv:1i3W/KWJCVG4F5uFDBttRyG9Z3BdyRa6XlkrkPNWkXQ=,tag:FjqH1Sfo1+1ALuUWAvrjyg==,type:str] sops: kms: [] gcp_kms: [] @@ -54,8 +56,8 @@ sops: TGk4dUlwcE9XWWIzZE1nQXdXcWY0V0kKJi5yXdrsEOP4Z8K6k/sPA7yadNPKQtzo Iyt//Y+Y7n55KwuO8Doogu42SiVTUhHDICM9lezQmcugFqCoh3Lk4A== -----END AGE ENCRYPTED FILE----- - lastmodified: "2023-07-26T09:49:19Z" - mac: ENC[AES256_GCM,data:sAJcUwJeVCXwNXmWUJrP0L1UcjoYDqErW2mBTRC3yoUOVtbVdZnLkswO0PARWruOqMBKXkIH/SqeiLyJ7HLIsobBzFoUNQ6TgjmP0OHf4Qbo/5sSDVA95qK1ZCgK93uKSEfG0WUvJLqfOKUEdBUgPUqJ58RM2VOWU21liccaG+A=,iv:u6lStYbzZsOWd5rsZXKs0XCAbQTFsPrnXLqO27i/Qt0=,tag:JeYtuP1zztsy4FUB1kzcWw==,type:str] + lastmodified: "2023-07-28T09:00:40Z" + mac: ENC[AES256_GCM,data:EJGv76KzHaWG80CZy4/1n9JmDl1JafIR4mfNl4uWJeeZqvJm3D47WbXXKeOVnMGuSvElqxnLELpXG+aSxkbxBxc7fGDTwXPlnSb6N81OP4lZ9NfA0VvXo3dQY5vjunGUVhkK+eyVDeE/pIaO/EpIeUiCNug+OzpM5AjNU5KQXYc=,iv:upGfihotn1k1v2QbSapRv1O6aynNRnKW0mqDxJ4JIQg=,tag:ZJTQlwBvRSaV4CK3V2hoRA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3