From ef46ad47b5c1b6b77604f6de28e0dd342654b9e9 Mon Sep 17 00:00:00 2001 From: toufic ar Date: Thu, 7 May 2026 02:13:21 +0300 Subject: nix support --- flake.lock | 27 +++++++ flake.nix | 82 ++++++++++++++++++++++ makeshiftci | 227 ----------------------------------------------------------- msci | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 336 insertions(+), 228 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix delete mode 100755 makeshiftci mode change 120000 => 100755 msci diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c6670e1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1777954456, + "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..661763b --- /dev/null +++ b/flake.nix @@ -0,0 +1,82 @@ +{ + description = "a makeshift CI solution"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { + self, + nixpkgs, + }: let + system = "x86_64-linux"; + pkgs = import nixpkgs {inherit system;}; + msci = (pkgs.writeScriptBin + "msci" (builtins.readFile ./msci)).overrideAttrs (old: { + buildCommand = "${old.buildCommand}\n patchShebangs $out"; + }); + in { + packages."${system}".msci = pkgs.symlinkJoin { + name = "msci"; + paths = [msci pkgs.jq pkgs.git]; + buildInputs = [pkgs.makeWrapper]; + postBuild = "wrapProgram $out/bin/msci --prefix PATH : $out/bin"; + }; + nixosModules.default = { + lib, + config, + ... + }: let + cfg = config.makeshiftci; + in { + options = with lib; { + makeshiftci = mkOption { + type = types.submodule { + options = { + enable = mkEnableOption "enable makeshiftci"; + dataDir = mkOption { + type = types.str; + default = "/var/lib/makeshiftci"; + description = "data directory of makeshiftci"; + }; + createUser = mkEnableOption "create a non-root user"; + }; + }; + }; + }; + config = lib.mkIf cfg.enable { + environment = { + sessionVariables.MSCI_HOME = cfg.dataDir; + systemPackages = [ + pkgs.jq + pkgs.git + self.packages."${system}".msci + ]; + }; + systemd.tmpfiles.settings."makeshiftci" = { + "${cfg.dataDir}" = { + d = { + user = + if cfg.createUser + then "makeshiftci" + else "root"; + group = + if cfg.createUser + then "makeshiftci" + else "root"; + mode = "0750"; + }; + }; + }; + services.cron.enable = true; + users = lib.mkIf cfg.createUser { + users."makeshiftci" = { + group = "makeshiftci"; + home = cfg.dataDir; + useDefaultShell = true; + }; + }; + }; + }; + }; +} diff --git a/makeshiftci b/makeshiftci deleted file mode 100755 index b2a533c..0000000 --- a/makeshiftci +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env bash - -set -e -shopt -s nullglob -mkdir -p "$MSCI_HOME"/projects \ - "$MSCI_HOME"/stdout/public \ - "$MSCI_HOME"/stdout/private \ - "$MSCI_HOME"/tmp -[ ! -f "$MSCI_HOME"/cron ] && touch "$MSCI_HOME"/cron - -confirm_in() { - read -rp "$1 (y/N): " confirm - [[ $confirm == [yY]* ]] && return 0 || return 1 -} - -find_project() { - if list_projects | grep -xFq -- "$1"; then - return 0 - fi - return 1 -} - -list_projects() { - paths=("$MSCI_HOME"/projects/*) - names=("${paths[@]##*/}") - projects=("${names[@]%.json}") - printf '%s\n' "${projects[@]}" -} - -write_cron() { - cfile="MSCI_HOME=$MSCI_HOME\n" - for project in $(list_projects); do - ppath="$MSCI_HOME"/projects/"$project".json - schedule=$(jq -r '.cron' "$ppath") - [ "$schedule" = 'null' ] && continue - cfile="${cfile}$schedule $MSCI_HOME/makeshiftci run $project\n" - done - echo -e "$cfile" >"$MSCI_HOME"/tmp.cron - if crontab "$MSCI_HOME"/tmp.cron; then - mv -fv "$MSCI_HOME"/tmp.cron "$MSCI_HOME"/cron - else - rm -fv "$MSCI_HOME"/tmp.cron - fi -} - -run_project() { - ppath="$MSCI_HOME"/projects/"$1".json - repo_name=$(jq -r '.name' "$ppath") - repo_url=$(jq -r '.url' "$ppath") - repo_path="$MSCI_HOME"/tmp/"$repo_name" - echo "cloning repo: $repo_url" | tee -a "$2" - git clone --quiet "$repo_url" "$repo_path" - pushd "$repo_path" >/dev/null || return 1 - for job in "$repo_path"/.makeshiftci/*; do - pname=$(jq -r '.name' "$job") - echo "running job: $pname" | tee -a "$2" - pimage=$(jq -r '.image' "$job") - psecrets=$(jq -r '.secrets' "$job") - prun=$(jq -r '.run[]' "$job") - env_secrets=() - secret_mounts=() - if [ ! "$psecrets" = 'null' ]; then - for secret_key in $(echo "$psecrets" | jq -r '. | keys[]'); do - secret_value=$(echo "$psecrets" | jq -r ".$secret_key") - secret_name=$(openssl rand -hex 16) - secret_mounts+=("--mount=type=bind,source=$secret_value,target=/$secret_name,readonly") - env_secrets+=("$secret_key=/$secret_name") - done - fi - docker run --rm \ - "${env_secrets[@]/#/--env=}" \ - --mount type=bind,source="$repo_path",target=/"$repo_name" \ - "${secret_mounts[@]}" \ - --workdir="/$repo_name" \ - "$pimage" \ - sh -c "exec $prun" | tee -a "$2" - done - popd >/dev/null && rm -rf "$repo_path" - echo "finished" | tee -a "$2" -} - -create_project() { - name="$1" - if find_project "$name"; then - echo "project file already exists" - return 1 - fi - read -rp "project name: " pname - read -rp ".makeshiftci repo URL: " purl - phidden=$(confirm_in "hidden?" && echo true || echo false) - read -rp "cron (optional): " pcron - jq -n \ - --arg pname "$pname" \ - --arg purl "$purl" \ - --argjson phidden "$phidden" \ - --arg pcron "$pcron" \ - '{ - name: $pname, - url: $purl, - hidden: $phidden, - cron: (if $pcron == "" then null else $pcron end) - }' \ - >"$MSCI_HOME"/projects/"$name".json - [ -n "$pcron" ] && write_cron - ([ "$phidden" = "true" ] && - mkdir -p "$MSCI_HOME"/stdout/private/"$1") || - mkdir -p "$MSCI_HOME"/stdout/public/"$1" -} - -edit_project() { - name="$1" - if ! find_project "$name"; then - echo "project file doesn't exist" - return 1 - fi - ppath="$MSCI_HOME"/projects/"$name".json - echo "editing $ppath" - oldname=$(jq -r '.name' "$ppath") - oldurl=$(jq -r '.url' "$ppath") - washidden=$(jq '.hidden' "$ppath") - keephidden=$([ "$washidden" = 'true' ] && echo "keep" || echo "make") - oldcron=$(jq -r '.cron' "$ppath") - echo "project name: $oldname" - read -rp "new name (optional): " pname - [ -z "$pname" ] && pname=$oldname - echo ".makeshiftci repo URL: $oldurl" - read -rp "new URL (optional): " purl - [ -z "$purl" ] && purl=$oldurl - echo "hidden: $washidden" - phidden=$(confirm_in "$keephidden hidden?" && echo true || echo false) - echo "cron: $oldcron" - read -rp "new cron (optional): " pcron - [ -z "$pcron" ] && pcron=$oldcron - jq -n \ - --arg pname "$pname" \ - --arg purl "$purl" \ - --argjson phidden "$phidden" \ - --arg pcron "$pcron" \ - '{ - name: $pname, - url: $purl, - hidden: $phidden, - cron: (if $pcron == "null" then null else $pcron end) - }' \ - >"$ppath" - [ ! "$pcron" = "$oldcron" ] && write_cron - privout="$MSCI_HOME"/stdout/private/"$1" - pubout="$MSCI_HOME"/stdout/public/"$1" - ( - [ "$phidden" = "true" ] && - ([ ! -d "$privout" ] && mv "$pubout" "$privout") - ) || - ([ ! -d "$pubout" ] && mv "$privout" "$pubout") -} - -case $1 in -create) - if [ -z "$2" ]; then - echo "no project file name is supplied" - exit 1 - fi - create_project "$2" - ;; -edit) - if [ -z "$2" ]; then - echo "no project file name is supplied" - exit 1 - fi - edit_project "$2" - ;; -delete) - if [ -z "$2" ]; then - echo "no project file name is supplied" - exit 1 - fi - if ! find_project "$2"; then - echo "project file doesn't exist" - exit 1 - fi - ppath="$MSCI_HOME"/projects/"$2".json - cat "$ppath" - confirm_in "delete '$2' (details above)?" && - rm -vf "$MSCI_HOME"/projects/"$2".json || - exit 0 - rm -rf "$MSCI_HOME"/stdout/**/"$2" - write_cron - ;; -run) - if [ -z "$2" ]; then - echo "no project file name is supplied" - exit 1 - fi - if ! find_project "$2"; then - echo "project file doesn't exist" - exit 1 - fi - ppath="$MSCI_HOME"/projects/"$2".json - phidden=$(jq -r '.hidden' "$ppath") - stdout_type=$([ "$phidden" = 'false' ] && echo "public" || echo "private") - stdout_path="$MSCI_HOME"/stdout/"$stdout_type"/"$2" - last_run=$( - find "$stdout_path" \ - -maxdepth 1 \ - -mindepth 1 \ - -type f -printf '%f\n' | - grep -E '^[0-9]+$' | - sort -n | - tail -n 1 - ) - next_run=$( - [ -z "$last_run" ] && - echo "1" || - echo $((last_run + 1)) - ) - stdout_path="$stdout_path"/"$next_run" - run_project "$2" "$stdout_path" - ;; -list) - list_projects - ;; -*) - echo "unknown option '$1'" - exit 1 - ;; -esac - -# vim: set filetype=bash: diff --git a/msci b/msci deleted file mode 120000 index 701f6e9..0000000 --- a/msci +++ /dev/null @@ -1 +0,0 @@ -makeshiftci \ No newline at end of file diff --git a/msci b/msci new file mode 100755 index 0000000..b2a533c --- /dev/null +++ b/msci @@ -0,0 +1,227 @@ +#!/usr/bin/env bash + +set -e +shopt -s nullglob +mkdir -p "$MSCI_HOME"/projects \ + "$MSCI_HOME"/stdout/public \ + "$MSCI_HOME"/stdout/private \ + "$MSCI_HOME"/tmp +[ ! -f "$MSCI_HOME"/cron ] && touch "$MSCI_HOME"/cron + +confirm_in() { + read -rp "$1 (y/N): " confirm + [[ $confirm == [yY]* ]] && return 0 || return 1 +} + +find_project() { + if list_projects | grep -xFq -- "$1"; then + return 0 + fi + return 1 +} + +list_projects() { + paths=("$MSCI_HOME"/projects/*) + names=("${paths[@]##*/}") + projects=("${names[@]%.json}") + printf '%s\n' "${projects[@]}" +} + +write_cron() { + cfile="MSCI_HOME=$MSCI_HOME\n" + for project in $(list_projects); do + ppath="$MSCI_HOME"/projects/"$project".json + schedule=$(jq -r '.cron' "$ppath") + [ "$schedule" = 'null' ] && continue + cfile="${cfile}$schedule $MSCI_HOME/makeshiftci run $project\n" + done + echo -e "$cfile" >"$MSCI_HOME"/tmp.cron + if crontab "$MSCI_HOME"/tmp.cron; then + mv -fv "$MSCI_HOME"/tmp.cron "$MSCI_HOME"/cron + else + rm -fv "$MSCI_HOME"/tmp.cron + fi +} + +run_project() { + ppath="$MSCI_HOME"/projects/"$1".json + repo_name=$(jq -r '.name' "$ppath") + repo_url=$(jq -r '.url' "$ppath") + repo_path="$MSCI_HOME"/tmp/"$repo_name" + echo "cloning repo: $repo_url" | tee -a "$2" + git clone --quiet "$repo_url" "$repo_path" + pushd "$repo_path" >/dev/null || return 1 + for job in "$repo_path"/.makeshiftci/*; do + pname=$(jq -r '.name' "$job") + echo "running job: $pname" | tee -a "$2" + pimage=$(jq -r '.image' "$job") + psecrets=$(jq -r '.secrets' "$job") + prun=$(jq -r '.run[]' "$job") + env_secrets=() + secret_mounts=() + if [ ! "$psecrets" = 'null' ]; then + for secret_key in $(echo "$psecrets" | jq -r '. | keys[]'); do + secret_value=$(echo "$psecrets" | jq -r ".$secret_key") + secret_name=$(openssl rand -hex 16) + secret_mounts+=("--mount=type=bind,source=$secret_value,target=/$secret_name,readonly") + env_secrets+=("$secret_key=/$secret_name") + done + fi + docker run --rm \ + "${env_secrets[@]/#/--env=}" \ + --mount type=bind,source="$repo_path",target=/"$repo_name" \ + "${secret_mounts[@]}" \ + --workdir="/$repo_name" \ + "$pimage" \ + sh -c "exec $prun" | tee -a "$2" + done + popd >/dev/null && rm -rf "$repo_path" + echo "finished" | tee -a "$2" +} + +create_project() { + name="$1" + if find_project "$name"; then + echo "project file already exists" + return 1 + fi + read -rp "project name: " pname + read -rp ".makeshiftci repo URL: " purl + phidden=$(confirm_in "hidden?" && echo true || echo false) + read -rp "cron (optional): " pcron + jq -n \ + --arg pname "$pname" \ + --arg purl "$purl" \ + --argjson phidden "$phidden" \ + --arg pcron "$pcron" \ + '{ + name: $pname, + url: $purl, + hidden: $phidden, + cron: (if $pcron == "" then null else $pcron end) + }' \ + >"$MSCI_HOME"/projects/"$name".json + [ -n "$pcron" ] && write_cron + ([ "$phidden" = "true" ] && + mkdir -p "$MSCI_HOME"/stdout/private/"$1") || + mkdir -p "$MSCI_HOME"/stdout/public/"$1" +} + +edit_project() { + name="$1" + if ! find_project "$name"; then + echo "project file doesn't exist" + return 1 + fi + ppath="$MSCI_HOME"/projects/"$name".json + echo "editing $ppath" + oldname=$(jq -r '.name' "$ppath") + oldurl=$(jq -r '.url' "$ppath") + washidden=$(jq '.hidden' "$ppath") + keephidden=$([ "$washidden" = 'true' ] && echo "keep" || echo "make") + oldcron=$(jq -r '.cron' "$ppath") + echo "project name: $oldname" + read -rp "new name (optional): " pname + [ -z "$pname" ] && pname=$oldname + echo ".makeshiftci repo URL: $oldurl" + read -rp "new URL (optional): " purl + [ -z "$purl" ] && purl=$oldurl + echo "hidden: $washidden" + phidden=$(confirm_in "$keephidden hidden?" && echo true || echo false) + echo "cron: $oldcron" + read -rp "new cron (optional): " pcron + [ -z "$pcron" ] && pcron=$oldcron + jq -n \ + --arg pname "$pname" \ + --arg purl "$purl" \ + --argjson phidden "$phidden" \ + --arg pcron "$pcron" \ + '{ + name: $pname, + url: $purl, + hidden: $phidden, + cron: (if $pcron == "null" then null else $pcron end) + }' \ + >"$ppath" + [ ! "$pcron" = "$oldcron" ] && write_cron + privout="$MSCI_HOME"/stdout/private/"$1" + pubout="$MSCI_HOME"/stdout/public/"$1" + ( + [ "$phidden" = "true" ] && + ([ ! -d "$privout" ] && mv "$pubout" "$privout") + ) || + ([ ! -d "$pubout" ] && mv "$privout" "$pubout") +} + +case $1 in +create) + if [ -z "$2" ]; then + echo "no project file name is supplied" + exit 1 + fi + create_project "$2" + ;; +edit) + if [ -z "$2" ]; then + echo "no project file name is supplied" + exit 1 + fi + edit_project "$2" + ;; +delete) + if [ -z "$2" ]; then + echo "no project file name is supplied" + exit 1 + fi + if ! find_project "$2"; then + echo "project file doesn't exist" + exit 1 + fi + ppath="$MSCI_HOME"/projects/"$2".json + cat "$ppath" + confirm_in "delete '$2' (details above)?" && + rm -vf "$MSCI_HOME"/projects/"$2".json || + exit 0 + rm -rf "$MSCI_HOME"/stdout/**/"$2" + write_cron + ;; +run) + if [ -z "$2" ]; then + echo "no project file name is supplied" + exit 1 + fi + if ! find_project "$2"; then + echo "project file doesn't exist" + exit 1 + fi + ppath="$MSCI_HOME"/projects/"$2".json + phidden=$(jq -r '.hidden' "$ppath") + stdout_type=$([ "$phidden" = 'false' ] && echo "public" || echo "private") + stdout_path="$MSCI_HOME"/stdout/"$stdout_type"/"$2" + last_run=$( + find "$stdout_path" \ + -maxdepth 1 \ + -mindepth 1 \ + -type f -printf '%f\n' | + grep -E '^[0-9]+$' | + sort -n | + tail -n 1 + ) + next_run=$( + [ -z "$last_run" ] && + echo "1" || + echo $((last_run + 1)) + ) + stdout_path="$stdout_path"/"$next_run" + run_project "$2" "$stdout_path" + ;; +list) + list_projects + ;; +*) + echo "unknown option '$1'" + exit 1 + ;; +esac + +# vim: set filetype=bash: -- cgit v1.2.3