aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flake.lock27
-rw-r--r--flake.nix82
-rwxr-xr-xmakeshiftci227
-rwxr-xr-x[l---------]msci228
4 files changed, 336 insertions, 228 deletions
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
index 701f6e9..b2a533c 120000..100755
--- a/msci
+++ b/msci
@@ -1 +1,227 @@
-makeshiftci \ No newline at end of file
+#!/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: