{"id":44338700,"url":"https://github.com/initialed85/cnd","last_synced_at":"2026-02-11T12:17:47.211Z","repository":{"id":61624735,"uuid":"539414093","full_name":"initialed85/cnd","owner":"initialed85","description":"A simple commandline tool to help you automate things","archived":false,"fork":false,"pushed_at":"2024-05-25T14:05:18.000Z","size":4427,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-14T02:51:50.176Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/initialed85.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-21T09:47:30.000Z","updated_at":"2024-05-25T14:05:21.000Z","dependencies_parsed_at":"2024-06-20T12:58:52.954Z","dependency_job_id":"6f6d0807-176b-41ad-af03-a2b156948203","html_url":"https://github.com/initialed85/cnd","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/initialed85/cnd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/initialed85%2Fcnd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/initialed85%2Fcnd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/initialed85%2Fcnd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/initialed85%2Fcnd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/initialed85","download_url":"https://codeload.github.com/initialed85/cnd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/initialed85%2Fcnd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29332787,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T06:13:03.264Z","status":"ssl_error","status_checked_at":"2026-02-11T06:12:55.843Z","response_time":97,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-11T12:17:46.452Z","updated_at":"2026-02-11T12:17:47.200Z","avatar_url":"https://github.com/initialed85.png","language":"Go","readme":"# cnd\n\n# status: working and in production (for myself only as far as I'm aware)\n\nA simple commandline tool to help you automate things; `cnd` is short for `check-n-do` and provides some sugar\nas long as your problem fits the following pattern:\n\n-   Check the state of something against the last time it was checked\n-   If changed, execute an action\n\n## Concept\n\nThe user can invoke `cnd` as many times as they like, the given `check` will execute but the given `do` will only\nexecute if the result of\nthe `check` differed from the last time `cnd` was ran.\n\n## Installation\n\n```shell\ngo install github.com/initialed85/cnd@latest\n```\n\n## Usage\n\n```shell\ncnd \\\n  -job '(an optional job name; defaults to the current working directory name)' \\\n  -check '(a command that gathers our comparison state)' \\\n  -do '(a command that executes an action)'\n```\n\n## Example\n\n### Redeploying a service when a Git repo changes\n\n```shell\ncnd -check 'git pull \u003e/dev/null 2\u003e\u00261 \u0026\u0026 git log -b master HEAD~1..HEAD' -do 'make down ; make up'\n```\n\nWhen executed and there are no changes, the output will look something like this:\n\n```shell\n2022/09/21 09:03:36 check-n-do; jobName=\"dinosaur\"\n2022/09/21 09:03:36 check; command=\"git pull \u003e/dev/null 2\u003e\u00261 \u0026\u0026 git log -b master HEAD~1..HEAD\"\n2022/09/21 09:03:38 check; output=\"commit e03e2d359daf575e242b15c1c8d70a013b85b199\\nAuthor: Edward Beech \u003cinitialed85@gmail.com\u003e\\nDate:   Wed Sep 21 16:57:04 2022 +0800\\n\\n    Empty commit to see if my ghetto CD stuff is working\"\n2022/09/21 09:03:38 check; lastOutput=\"commit e03e2d359daf575e242b15c1c8d70a013b85b199\\nAuthor: Edward Beech \u003cinitialed85@gmail.com\u003e\\nDate:   Wed Sep 21 16:57:04 2022 +0800\\n\\n    Empty commit to see if my ghetto CD stuff is working\"\n2022/09/21 09:03:38 check; changed=false\n```\n\nAnd if there are changes, the output will look something like this:\n\n```shell\n2022/09/21 09:04:36 check-n-do; jobName=\"dinosaur\"\n2022/09/21 09:04:36 check; command=\"git pull \u003e/dev/null 2\u003e\u00261 \u0026\u0026 git log -b master HEAD~1..HEAD\"\n2022/09/21 09:04:39 check; output=\"commit c8d7ccd6d0c820f14856e33f48fce6c3a1a634db\\nAuthor: Edward Beech \u003cinitialed85@gmail.com\u003e\\nDate:   Wed Sep 21 17:04:29 2022 +0800\\n\\n    Another empty commit to see if my ghetto CD stuff is working\"\n2022/09/21 09:04:39 check; lastOutput=\"commit e03e2d359daf575e242b15c1c8d70a013b85b199\\nAuthor: Edward Beech \u003cinitialed85@gmail.com\u003e\\nDate:   Wed Sep 21 16:57:04 2022 +0800\\n\\n    Empty commit to see if my ghetto CD stuff is working\"\n2022/09/21 09:04:39 check; changed=true\n2022/09/21 09:04:39 do; command=\"make down ; make up\"\n2022/09/21 09:04:44 do; output=\"docker compose -f docker/docker-compose.yml down --remove-orphans --volumes || true\\nContainer docker-frontend-1  Stopping\\nContainer docker-frontend-1  Stopping\\nContainer docker-backend-1  Stopping\\nContainer docker-backend-1  Stopping\\nContainer docker-backend-1  Stopped\\nContainer docker-backend-1  Removing\\nContainer docker-backend-1  Removed\\nContainer docker-frontend-1  Stopped\\nContainer docker-frontend-1  Removing\\nContainer docker-frontend-1  Removed\\nNetwork dinosaur-internal  Removing\\nNetwork dinosaur-external  Removing\\nNetwork dinosaur-internal  Removed\\nNetwork dinosaur-external  Removed\\ndocker build -t dinosaur-session -f docker/session/Dockerfile ./docker/session\\nSending build context to Docker daemon  8.704kB\\r\\r\\nStep 1/22 : FROM ubuntu:22.04\\n ---\u003e 2dc39ba059dc\\nStep 2/22 : RUN apt-get update \u0026\u0026     apt-get install -y     curl git npm entr     golang-1.18 python3 default-jdk lua5.4 luarocks     procps file strace screen     net-tools inetutils-ping traceroute netcat tcpdump iproute2\\n ---\u003e Using cache\\n ---\u003e e945329d79f9\\nStep 3/22 : RUN npm install -g typescript ts-node\\n ---\u003e Using cache\\n ---\u003e 46717d934463\\nStep 4/22 : RUN curl https://sh.rustup.rs -sSf | sh -s -- -y\\n ---\u003e Using cache\\n ---\u003e 518759e3badb\\nStep 5/22 : ENV PATH=${PATH}:/usr/lib/go-1.18/bin:/root/go/bin/:/root/.cargo/bin\\n ---\u003e Using cache\\n ---\u003e 0ab91dbee20b\\nStep 6/22 : RUN go install github.com/sorenisanerd/gotty@latest\\n ---\u003e Using cache\\n ---\u003e ca817ab25f1b\\nStep 7/22 : RUN mkdir -p /srv/cmd/\\n ---\u003e Using cache\\n ---\u003e 0c226f0dc3f9\\nStep 8/22 : WORKDIR /srv/\\n ---\u003e Using cache\\n ---\u003e 9383bb62d031\\nStep 9/22 : RUN luarocks install luasocket\\n ---\u003e Using cache\\n ---\u003e f5529435e81c\\nStep 10/22 : RUN npm i --save-dev @types/node\\n ---\u003e Using cache\\n ---\u003e 7aa1a6c9f2ef\\nStep 11/22 : RUN echo 'termcapinfo xterm* ti@:te@' \u003e\u003e /root/.screenrc\\n ---\u003e Using cache\\n ---\u003e 8b8e167d5e6d\\nStep 12/22 : COPY docker-entrypoint.sh /docker-entrypoint.sh\\n ---\u003e Using cache\\n ---\u003e 91b0e7c1d04e\\nStep 13/22 : COPY loop.sh /loop.sh\\n ---\u003e Using cache\\n ---\u003e 1baa4281d86d\\nStep 14/22 : COPY build.sh /build.sh\\n ---\u003e Using cache\\n ---\u003e b779bc5c4f28\\nStep 15/22 : COPY run.sh /run.sh\\n ---\u003e Using cache\\n ---\u003e d866e4bfe5f9\\nStep 16/22 : COPY watch.sh /watch.sh\\n ---\u003e Using cache\\n ---\u003e eb766bb51055\\nStep 17/22 : ENV GOTTY_PORT=${GOTTY_PORT:-8080}\\n ---\u003e Using cache\\n ---\u003e 63577e48f262\\nStep 18/22 : ENV GOTTY_PATH=${GOTTY_PATH}\\n ---\u003e Using cache\\n ---\u003e 799685a63c38\\nStep 19/22 : ENV BASE_FOLDER_PATH=${BASE_FOLDER_PATH:-/srv/}\\n ---\u003e Using cache\\n ---\u003e 64be509c0a1d\\nStep 20/22 : ENV BUILD_CMD=${BUILD_CMD}\\n ---\u003e Using cache\\n ---\u003e 0b290e0759b2\\nStep 21/22 : ENV RUN_CMD=${RUN_CMD}\\n ---\u003e Using cache\\n ---\u003e f5758eec0880\\nStep 22/22 : ENTRYPOINT [\\\"/docker-entrypoint.sh\\\"]\\n ---\u003e Using cache\\n ---\u003e 08ee7a337db7\\nSuccessfully built 08ee7a337db7\\nSuccessfully tagged dinosaur-session:latest\\ndocker compose -f docker/docker-compose.yml build --parallel\\n#1 [docker-backend internal] load build definition from Dockerfile\\n#1 transferring dockerfile: 32B 0.0s done\\n#1 DONE 0.1s\\n\\n#2 [docker-frontend internal] load build definition from Dockerfile\\n#2 transferring dockerfile: 32B 0.1s done\\n#2 DONE 0.1s\\n\\n#3 [docker-frontend internal] load .dockerignore\\n#3 transferring context: 2B done\\n#3 DONE 0.0s\\n\\n#4 [docker-backend internal] load .dockerignore\\n#4 transferring context: 2B 0.0s done\\n#4 DONE 0.0s\\n\\n#5 [docker-backend internal] load metadata for docker.io/library/ubuntu:22.04\\n#5 DONE 0.0s\\n\\n#6 [docker-backend internal] load metadata for docker.io/library/golang:1.18\\n#6 DONE 1.9s\\n\\n#7 [docker-frontend internal] load metadata for docker.io/library/node:14.20.0\\n#7 DONE 1.9s\\n\\n#8 [docker-backend stage-1  1/11] FROM docker.io/library/ubuntu:22.04\\n#8 DONE 0.0s\\n\\n#9 [docker-backend build 1/9] FROM docker.io/library/golang:1.18@sha256:040bc0980888e2df09499c73156f3f869d843b7033d11b465fee7f1a358a494a\\n#9 DONE 0.0s\\n\\n#10 [docker-backend internal] load build context\\n#10 transferring context: 2.57kB 0.0s done\\n#10 DONE 0.0s\\n\\n#11 [docker-backend stage-1  7/11] RUN mkdir -p /srv/pkg/sessions/languages\\n#11 CACHED\\n\\n#12 [docker-backend stage-1  3/11] RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\\n#12 CACHED\\n\\n#13 [docker-backend stage-1  6/11] WORKDIR /srv/\\n#13 CACHED\\n\\n#14 [docker-backend build 6/9] COPY cmd /srv/cmd\\n#14 CACHED\\n\\n#15 [docker-backend stage-1  8/11] COPY pkg/sessions/languages /srv/pkg/sessions/languages\\n#15 CACHED\\n\\n#16 [docker-backend stage-1 10/11] COPY docker/session /srv/docker/session\\n#16 CACHED\\n\\n#17 [docker-backend stage-1  9/11] RUN mkdir -p /srv/docker/\\n#17 CACHED\\n\\n#18 [docker-backend build 3/9] COPY go.mod /srv/go.mod\\n#18 CACHED\\n\\n#19 [docker-backend build 8/9] COPY pkg /srv/pkg\\n#19 CACHED\\n\\n#20 [docker-backend build 7/9] COPY internal /srv/internal\\n#20 CACHED\\n\\n#21 [docker-backend stage-1  2/11] RUN apt-get update \u0026\u0026 apt-get install -y ca-certificates curl gnupg lsb-release golang-1.18\\n#21 CACHED\\n\\n#22 [docker-backend build 5/9] RUN --mount=type=cache,target=/root/.cache/go-build go mod download\\n#22 CACHED\\n\\n#23 [docker-backend stage-1  4/11] RUN echo   \\\"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu   $(lsb_release -cs) stable\\\" | tee /etc/apt/sources.list.d/docker.list \u003e /dev/null\\n#23 CACHED\\n\\n#24 [docker-backend build 9/9] RUN --mount=type=cache,target=/root/.cache/go-build go build -v -o main /srv/cmd/main.go\\n#24 CACHED\\n\\n#25 [docker-backend build 2/9] WORKDIR /srv/\\n#25 CACHED\\n\\n#26 [docker-backend stage-1  5/11] RUN apt-get update \u0026\u0026 apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin\\n#26 CACHED\\n\\n#27 [docker-backend build 4/9] COPY go.sum /srv/go.sum\\n#27 CACHED\\n\\n#28 [docker-backend stage-1 11/11] COPY --from=build /srv/main /srv/main\\n#28 CACHED\\n\\n#29 [docker-frontend internal] load metadata for docker.io/library/nginx:1.23.1\\n#29 DONE 1.9s\\n\\n#30 [docker-backend] exporting to image\\n#30 exporting layers done\\n#30 writing image sha256:a638b17a3f22988e74afc7a11f62b042103b882729fa578ed78ed01fca23813a done\\n#30 naming to docker.io/library/docker-backend done\\n#30 DONE 0.0s\\n\\n#31 [docker-frontend build 1/9] FROM docker.io/library/node:14.20.0@sha256:6adfb0c2a9db12a06893974bb140493a7482e2b3df59c058590594ceecd0c99b\\n#31 DONE 0.0s\\n\\n#32 [docker-frontend stage-1 1/4] FROM docker.io/library/nginx:1.23.1@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1\\n#32 DONE 0.0s\\n\\n#33 [docker-frontend internal] load build context\\n#33 transferring context: 1.20MB 0.0s done\\n#33 DONE 0.0s\\n\\n#34 [docker-frontend build 6/9] COPY frontend/tsconfig.json /srv/tsconfig.json\\n#34 CACHED\\n\\n#35 [docker-frontend build 8/9] COPY frontend/public /srv/public\\n#35 CACHED\\n\\n#36 [docker-frontend build 4/9] COPY frontend/package-lock.json /srv/package-lock.json\\n#36 CACHED\\n\\n#37 [docker-frontend stage-1 2/4] COPY docker/frontend/default.conf /etc/nginx/conf.d/default.conf\\n#37 CACHED\\n\\n#38 [docker-frontend build 3/9] COPY frontend/package.json /srv/package.json\\n#38 CACHED\\n\\n#39 [docker-frontend build 7/9] COPY frontend/src /srv/src\\n#39 CACHED\\n\\n#40 [docker-frontend build 9/9] RUN npm run build\\n#40 CACHED\\n\\n#41 [docker-frontend stage-1 3/4] COPY --from=build /srv/build/static /usr/share/nginx/html/static\\n#41 CACHED\\n\\n#42 [docker-frontend build 2/9] WORKDIR /srv/\\n#42 CACHED\\n\\n#43 [docker-frontend build 5/9] RUN npm ci\\n#43 CACHED\\n\\n#44 [docker-frontend stage-1 4/4] COPY --from=build /srv/build/* /usr/share/nginx/html/\\n#44 CACHED\\n\\n#30 [docker-frontend] exporting to image\\n#30 exporting layers done\\n#30 writing image sha256:6458f8593ff555f1a6ef183ea5d2f0e1402538bcb1226ea9934cbb5ddc1790f8 done\\n#30 naming to docker.io/library/docker-frontend done\\n#30 DONE 0.0s\\n\\nUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them\\ndocker compose -f docker/docker-compose.yml up -d\\nNetwork dinosaur-internal  Creating\\nNetwork dinosaur-internal  Created\\nNetwork dinosaur-external  Creating\\nNetwork dinosaur-external  Created\\nContainer docker-frontend-1  Creating\\nContainer docker-backend-1  Creating\\nContainer docker-frontend-1  Created\\nContainer docker-backend-1  Created\\nContainer docker-backend-1  Starting\\nContainer docker-frontend-1  Starting\\nContainer docker-frontend-1  Started\\nContainer docker-backend-1  Started\"\n2022/09/21 09:04:44 check-n-do; done=true\n```\n\nIf you wanted to run the above every minute as a `cron` job; edit your `crontab` with `crontab -e` and add something\nlike the following:\n\n```shell\n  * *  *   *   *     PATH=${PATH}:${HOME}/go/bin /usr/bin/bash -c \"cd ${HOME}/Projects/Home/dinosaur \u0026\u0026 cnd -check 'git pull \u003e/dev/null 2\u003e\u00261 \u0026\u0026 git log -b master HEAD~1..HEAD' -do 'make down ; make up'\"\n```\n\nI find `cron` to be a real pain in the neck (at least for Ubuntu) with its default shell, path etc so all that\nboilerplate helps make that transparent.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finitialed85%2Fcnd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finitialed85%2Fcnd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finitialed85%2Fcnd/lists"}