├── .kanban ├── .kanban.conf ├── .kanban.csv └── IDEA.TXT ├── .test ├── Dockerfile ├── docker-entrypoint.sh ├── test └── tests │ ├── 10_startcontainer │ ├── 20_init_repo │ ├── 22_auto_populate_remotes │ ├── 30_repo_push │ ├── 82_recipe_envfile │ ├── 83_recipe_rollback │ ├── 86_recipe_baremetal │ ├── 86_recipe_baremetal_webcli │ ├── 87_recipe_container │ ├── 87_recipe_container_webcli │ ├── 88_recipe_baremetal_autosuspend │ ├── 89_recipe_container_autosuspend │ └── 90_leproxy ├── LICENSE ├── README.md ├── doc ├── extend.svg ├── intro.svg ├── recipes.md ├── systemd.md └── workflow.jpg ├── podi └── recipe ├── app └── container │ └── redbean ├── checkout └── rollback_simple ├── hello ├── helloworld.yaml ├── index.txt ├── init ├── gitops │ └── jail └── server │ └── sshkey ├── pipeline ├── run ├── baremetal ├── baremetal_autosuspend ├── baremetal_webcli ├── container ├── container_autosuspend ├── container_compose └── container_webcli └── start └── envfile /.kanban/.kanban.conf: -------------------------------------------------------------------------------- 1 | # kanban config file 2 | statuses=('TODO' 'HOLD' 'DOING' 'DONE' 'NOTES' 'BACKLOG') 3 | 4 | XSMALL=119 # show small kanban for terminalwidth < 119 chars 5 | SMALLSCREEN=('DOING' 'TODO' 'HOLD') # define simplified kanban board statuses 6 | 7 | # maximum amount of todos within status (triggers warning when exceeds) 8 | declare -A maximum_todo 9 | maximum_todo[HOLD]=10 10 | maximum_todo[DOING]=5 11 | 12 | -------------------------------------------------------------------------------- /.kanban/.kanban.csv: -------------------------------------------------------------------------------- 1 | "TODO","feature",".podman/git@localhost/app => .podman/git@srv/app/config","T","2022-02-09@17:59" 2 | "TODO","feat","commitmsg env 'FOO=1'","T","2022-02-10@11:09" 3 | "TODO","feat","nginx as root-user","T","2022-02-10@11:09" 4 | "TODO","feta","jail recipe => init + prompt jail user?","T","2022-02-18@12:38" 5 | "TODO","feat","recipe/monitor (tmux)","T","2022-02-18@12:45" 6 | "TODO","feat","podi admin root .bashrc (machinectl)","T","2022-02-18@12:46" 7 | -------------------------------------------------------------------------------- /.kanban/IDEA.TXT: -------------------------------------------------------------------------------- 1 | 2 | # why? 3 | 4 | while others are writing kubernetes-optimized javascriptbundlers on a blockchain, some people focus on simplicity. 5 | -------------------------------------------------------------------------------- /.test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.9 2 | 3 | RUN apk update \ 4 | && echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \ 5 | && apk update \ 6 | && apk add openssh git socat \ 7 | && addgroup -S git && adduser -D -S git -G git -s /bin/sh \ 8 | && mkdir -p /home/git/.ssh \ 9 | && chmod 0700 /home/git/.ssh \ 10 | && chown git:git -R /home/git \ 11 | && ssh-keygen -A \ 12 | && sed -i s/^#PasswordAuthentication\ yes/PasswordAuthentication\ no/ /etc/ssh/sshd_config 13 | 14 | RUN echo "git:git" | chpasswd 15 | RUN chown -R git:git /home/git 16 | 17 | CMD ["/podi/.test/docker-entrypoint.sh"] 18 | -------------------------------------------------------------------------------- /.test/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "${AUTHORIZED_KEYS}" ]; then 3 | echo "Need your ssh public key as AUTHORIZED_KEYS env variable. Abnormal exit ..." 4 | exit 1 5 | fi 6 | 7 | echo "Populating /home/git/.ssh/authorized_keys with the value from AUTHORIZED_KEYS env variable ..." 8 | echo "${AUTHORIZED_KEYS}" > /home/git/.ssh/authorized_keys 9 | 10 | # Execute the CMD from the Dockerfile: 11 | exec /usr/sbin/sshd -D -e 12 | -------------------------------------------------------------------------------- /.test/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export PATH=$PATH:$(pwd) 3 | set -e 4 | 5 | which podman 2>/dev/null || alias podman=docker 6 | 7 | all(){ 8 | podman init 9 | } 10 | 11 | describe(){ 12 | C_NORMAL="\\033[0;0m"; C_CYAN="\\033[1;36m" 13 | set +ex 14 | printf "\n$C_CYAN##################### %s ######################$C_NORMAL\n\n" "$(basename $1)" 15 | set -ex 16 | } 17 | 18 | run(){ 19 | find .test/tests -type f | sort | xargs -n1 bash -c ; echo errors=$PIPESTATUS 20 | } 21 | 22 | "$@" 23 | -------------------------------------------------------------------------------- /.test/tests/10_startcontainer: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | set +x 4 | podman build ./.test -t podi 5 | podman rm -f podi 6 | podman run -d -p 2222:22 -p 9999:9999 -e AUTHORIZED_KEYS="$(cat ~/.ssh/*.pub)" -v $(pwd):/podi -w /home/git --name podi podi 7 | 8 | echo docker run -d --privileged --name 2WA -p 2222:22 -p 8888:80 -v $(readlink -f ~/.ssh):/root/.ssh:ro --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro --volume=$(pwd):/etc/ansible/roles/test:ro ubuntu2004-ansible 9 | -------------------------------------------------------------------------------- /.test/tests/20_init_repo: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -d app && rm -rf app 4 | mkdir app 5 | cd app 6 | cp ../podi . 7 | git init 8 | ./podi init git@localhost:/home/git/app master 2222 app1 9 | -------------------------------------------------------------------------------- /.test/tests/22_auto_populate_remotes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderofsalvation/podi/d87be4a300de9a05fd9d5ee65eccce381ace6da7/.test/tests/22_auto_populate_remotes -------------------------------------------------------------------------------- /.test/tests/30_repo_push: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | cd app 4 | echo "# $(date)" >> .pod/pipeline 5 | git add .pod podi && git commit -m "testchange" 6 | git push app1 master 7 | -------------------------------------------------------------------------------- /.test/tests/82_recipe_envfile: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | set -x 4 | cd app 5 | ./podi init git@localhost:/home/git/app2 master 2222 6 | git remote -v 7 | cp ../recipe/start/envfile .pod/. 8 | ./podi envset 9 | git add .pod .env podi 10 | git commit -m "recipe envfile" 11 | git remote -v 12 | git push app2 master 13 | ECHO TODO #1: store remotes in .podman/remotename 14 | ECHO TODO #2: ./podi ==> list remotes + add git remotes if not exist 15 | exit 1 16 | #./podi envset app2 FOO=bar 17 | #./podi envset app2 FOO="flop space" 18 | #./podi envset app2 XYZ=1 19 | #./podi envset 20 | -------------------------------------------------------------------------------- /.test/tests/83_recipe_rollback: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | cd app 4 | cp ../recipe/extract/rollback_simple .pod/. 5 | git add .pod 6 | currentcommit=$(git log -n 1 --format="%h") 7 | git commit -m "recipe rollback" 8 | git push app1 master 9 | ./podi rollback git@localhost app1 $currentcommit 10 | -------------------------------------------------------------------------------- /.test/tests/86_recipe_baremetal: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -d app && rm -rf app 4 | mkdir app 5 | cd app 6 | cp ../podi . 7 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 8 | ./podi recipe run/baremetal 9 | ./podi init_runtime 10 | ./podi init git@localhost:/home/git/app5 master 2222 11 | ./podi envset git@localhost app5 PORT=9999 12 | git add .pod app .env 13 | git commit -m "recipe app/simple" 14 | git push app5 master 15 | -------------------------------------------------------------------------------- /.test/tests/86_recipe_baremetal_webcli: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -d app && rm -rf app 4 | mkdir app 5 | cd app 6 | cp ../podi . 7 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 8 | set -x 9 | ./podi recipe run/baremetal_webcli 10 | ./podi init_runtime 11 | ./podi init $HOST:/home/git/app5 master $PORT 12 | ./podi envset $HOST app5 PORT=9001 13 | git add .pod app .env 14 | git commit -m "recipe app/simple" 15 | git push app5 master 16 | -------------------------------------------------------------------------------- /.test/tests/87_recipe_container: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -z $HOST && { echo "ERROR: please provide docker-server using HOST=git@localhost e.g."; exit; } 4 | GITPATH=/home/git/test 5 | PORT=222 6 | ssh -p $PORT $HOST rm -rf /home/git/test 7 | test -d app && rm -rf app 8 | mkdir app 9 | cd app 10 | cp ../podi . 11 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 12 | ./podi recipe run/container 13 | ./podi init_runtime 14 | ./podi init $HOST:$GITPATH master $PORT 15 | ./podi envset $HOST test PORT=9999 16 | git add .pod app .env 17 | git commit -m "recipe app/simple" 18 | git push test master 19 | -------------------------------------------------------------------------------- /.test/tests/87_recipe_container_webcli: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -z $HOST && { echo "ERROR: please provide docker-server using HOST=git@localhost e.g."; exit; } 4 | GITPATH=/home/git/test 5 | ssh -p $PORT $HOST rm -rf /home/git/test 6 | test -d app && rm -rf app 7 | mkdir app 8 | cd app 9 | cp ../podi . 10 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 11 | ./podi recipe run/container_webcli 12 | ./podi init_runtime 13 | ./podi init $HOST:$GITPATH master $PORT 14 | ./podi envset $HOST test PORT=9999 15 | git add .pod app .env 16 | git commit -m "recipe app/simple" 17 | git push test master 18 | -------------------------------------------------------------------------------- /.test/tests/88_recipe_baremetal_autosuspend: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -d app && rm -rf app 4 | mkdir app 5 | cd app 6 | cp ../podi . 7 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 8 | ./podi recipe run/baremetal_autosuspend 9 | ./podi init_runtime 10 | ./podi init git@localhost:/home/git/app5 master 2222 11 | ./podi envset git@localhost app5 PORT=9999 12 | git add .pod app .env 13 | git commit -m "recipe app/simple" 14 | git push app5 master 15 | -------------------------------------------------------------------------------- /.test/tests/89_recipe_container_autosuspend: -------------------------------------------------------------------------------- 1 | .test/test describe $0 2 | 3 | test -z $HOST && { echo "ERROR: please provide docker-server using HOST=git@localhost e.g."; exit; } 4 | GITPATH=/home/git/test 5 | PORT=222 6 | set -x 7 | ssh -p $PORT $HOST rm -rf /home/git/test 8 | test -d app && rm -rf app 9 | mkdir app 10 | cd app 11 | cp ../podi . 12 | export RECIPE_REPOS=http://localhost:8081/recipe/index.txt 13 | ./podi recipe run/container_autosuspend 14 | ./podi init_runtime 15 | ./podi init $HOST:$GITPATH master $PORT 16 | ./podi envset $HOST test PORT=9998 17 | git add .pod app .env 18 | git commit -m "recipe app/simple" 19 | git push test master 20 | -------------------------------------------------------------------------------- /.test/tests/90_leproxy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderofsalvation/podi/d87be4a300de9a05fd9d5ee65eccce381ace6da7/.test/tests/90_leproxy -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022, Leon van Kammen / Coder of Salvation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > remove layers of complexity. 2 | 3 | ![](./doc/workflow.jpg) 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ cd myapp 9 | $ wget "https://raw.githubusercontent.com/coderofsalvation/podi/master/podi" 10 | $ chmod 755 podi 11 | ``` 12 | 13 | > PROFIT! now init your (ssh)server to enable a heroku-ish workflow: 14 | 15 | ![](./doc/intro.svg) 16 | 17 | ## Features 18 | 19 | * fully hackable PaaS & Gitops-designer (embedded in your repo) 20 | * multitenant: multi-branch and multi-sshuser deployments 21 | * `podi ls`: gitops templates for containerizing, autosuspending services on baremetal/podman/docker etc 22 | * hookable (`on build callmyfunction arg1`) 23 | * podi weighs ~7k, just needs ssh+git installed 24 | * works on raspberry pi zero but also on kubernetes 25 | 26 | ## Install 27 | 28 | ```bash 29 | $ wget "https://raw.githubusercontent.com/coderofsalvation/podi/master/podi" 30 | $ chmod 755 podi 31 | $ ./podi 32 | usage: 33 | init git@server:/dir/to/deploy [branch] [port] [name] initializes a deployment 34 | recipe installs a recipe from podi repo or url 35 | ``` 36 | 37 | ## Hooks / extend deployments 38 | 39 | ![](./doc/extend.svg) 40 | 41 | ## Templates 42 | 43 | ```bash 44 | $ ./podi init git@yourserver:/home/git/myapp master 45 | 46 | [✓] init 47 | [✓] init_localhost 48 | │ writing .pod/pipeline 49 | [?] how to run this app? 50 | 1 run/baremetal <-- perfect to run background services on lowend systems 51 | 2 run/baremetal_autosuspend <-- perfect for port-activated services on lowend systems 52 | 3 run/baremetal_webcli <-- perfect for outputting terminal-cmds to web 53 | 4 run/container <-- nice starting point for Dockerfile + app 54 | 5 run/container_autosuspend <-- port-activated container, nice starting point for Dockerfile + app 55 | 6 run/container_webcli <-- containerized cli, perfect for outputting terminal-cmds to web 56 | 7 run/container_compose <-- containerized stack, nice startingpoint using docker-compose.yml 57 | > ▉ 58 | ``` 59 | 60 | > IDEA: create your own! (see [adding your own recipes](doc/recipes.md) ) 61 | 62 | ## Docs 63 | 64 | * [adding your own recipes](doc/recipes.md) 65 | * [starting podi at server boot](doc/systemd.md) 66 | -------------------------------------------------------------------------------- /doc/intro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 201 | 240 | 241 | 242 | 243 | 244 | 245 | podidemo $ podidemo $ c podidemo $ cd podidemo $ cd podidemo $ cd / podidemo $ cd /t podidemo $ cd /tm podidemo $ cd /tmp podidemo $ cd /tmp/ podidemo $ cd /tmp/m podidemo $ cd /tmp/my podidemo $ cd /tmp/mya podidemo $ cd /tmp/myap podidemo $ cd /tmp/myapp podidemo $ cd /tmp/myapp podidemo $ l podidemo $ ls podidemo $ ls. .. app.go .gitpodidemo $ p podidemo $ po podidemo $ pod podidemo $ podi podidemo $ podi podidemo $ podi i podidemo $ podi in podidemo $ podi ini podidemo $ podi init podidemo $ podi init podidemo $ podi init g podidemo $ podi init gi podidemo $ podi init git podidemo $ podi init git@ podidemo $ podi init git@y podidemo $ podi init git@yo podidemo $ podi init git@you podidemo $ podi init git@your podidemo $ podi init git@yours podidemo $ podi init git@yourse podidemo $ podi init git@yourser podidemo $ podi init git@yourserv podidemo $ podi init git@yourserve podidemo $ podi init git@yourserver podidemo $ podi init git@yourserver: podidemo $ podi init git@yourserver:/ podidemo $ podi init git@yourserver:/h podidemo $ podi init git@yourserver:/ho podidemo $ podi init git@yourserver:/hom podidemo $ podi init git@yourserver:/home podidemo $ podi init git@yourserver:/home/ podidemo $ podi init git@yourserver:/home/g podidemo $ podi init git@yourserver:/home/gi podidemo $ podi init git@yourserver:/home/git podidemo $ podi init git@yourserver:/home/git/ podidemo $ podi init git@yourserver:/home/git/m podidemo $ podi init git@yourserver:/home/git/my podidemo $ podi init git@yourserver:/home/git/mya podidemo $ podi init git@yourserver:/home/git/myap podidemo $ podi init git@yourserver:/home/git/myapp podidemo $ podi init git@yourserver:/home/git/myapp podidemo $ podi init git@yourserver:/home/git/myapp m podidemo $ podi init git@yourserver:/home/git/myapp ma podidemo $ podi init git@yourserver:/home/git/myapp mas podidemo $ podi init git@yourserver:/home/git/myapp mast podidemo $ podi init git@yourserver:/home/git/myapp maste podidemo $ podi init git@yourserver:/home/git/myapp master podidemo $ podi init git@yourserver:/home/git/myapp master podidemo $ podi init git@yourserver:/home/git/myapp master 2 podidemo $ podi init git@yourserver:/home/git/myapp master 22 podidemo $ podi init git@yourserver:/home/git/myapp master 222 podidemo $ podi init git@yourserver:/home/git/myapp master 2222 podidemo $ podi init git@yourserver:/home/git/myapp master 2222init [] (leon@ls540)init_localhost [] (leon@ls540)init_server [] (leon@ls540) | init_gitops [] (git@yourserver) | init_post_receive [] (git@yourserver) └(you can now run: git push myapp master podidemo $ g podidemo $ gi podidemo $ git podidemo $ git podidemo $ git r podidemo $ git re podidemo $ git rem podidemo $ git remo podidemo $ git remot podidemo $ git remote podidemo $ git remotemyapporigin. .. app.go .git .pod podipodidemo $ git a podidemo $ git ad podidemo $ git add podidemo $ git add podidemo $ git add . podidemo $ git add .p podidemo $ git add .po podidemo $ git add .pod podidemo $ git add .pod podidemo $ git add .pod p podidemo $ git add .pod po podidemo $ git add .pod pod podidemo $ git add .pod podi podidemo $ git add .pod podipodidemo $ git c podidemo $ git co podidemo $ git com podidemo $ git comm podidemo $ git commi podidemo $ git commit podidemo $ git commit podidemo $ git commit - podidemo $ git commit -m podidemo $ git commit -m podidemo $ git commit -m p podidemo $ git commit -m po podidemo $ git commit -m pod podidemo $ git commit -m podi podidemo $ git commit -m podi[master 7c256d4] podi 3 files changed, 206 insertions(+) create mode 100644 .pod/git@yourserver/myapp/config create mode 100644 .pod/pipeline create mode 100755 podipodidemo $ git p podidemo $ git pu podidemo $ git pus podidemo $ git push podidemo $ git push podidemo $ git push m podidemo $ git push my podidemo $ git push mya podidemo $ git push myap podidemo $ git push myapp podidemo $ git push myapp podidemo $ git push myapp m podidemo $ git push myapp ma podidemo $ git push myapp mas podidemo $ git push myapp mast podidemo $ git push myapp maste podidemo $ git push myapp master podidemo $ git push myapp masterEnumerating objects: 9, done.Counting objects: 22% (2/9)Counting objects: 100% (9/9), done.Delta compression using up to 8 threadsCompressing objects: 100% (6/6), done.Writing objects: 100% (8/8), 3.05 KiB | 3.05 MiB/s, done.Total 8 (delta 0), reused 0 (delta 0), pack-reused 0remote: deploy [] (git@fcc36f233a6c) remote: 8b,dPPYba, ,adPPYba, ,adPPYb,88 88 remote: 88P` "8a a8" "8a a8" `Y88 88 remote: 88 d8 8b d8 8b 88 88 remote: 88b, ,a8" "8a, ,a8" "8a, ,d88 88 remote: 88`YbbdP"` `"YbbdP"` `"8bbdP"Y8 88 remote: 88 remote: 88 remote: remote: backup [] (git@fcc36f233a6c) remote: checkout [] (git@fcc36f233a6c) remote: build [] ( remote: build [] (git@fcc36f233a6c) remote: runtests [] (git@fcc36f233a6c) remote: start [] (git@fcc36f233a6c) To ssh://yourserver:2222/home/git/myapp/.git 1265ffc..7c256d4 master -> master 246 | 247 | -------------------------------------------------------------------------------- /doc/recipes.md: -------------------------------------------------------------------------------- 1 | # Adding your own recipes 2 | 3 | ```bash 4 | $ ./podi recipe hello # install helloworld-example 5 | $ mv .pod/{hello,foo} # rename to foo and modify 6 | $ git add .pod/foo 7 | # PROFIT! 8 | ``` 9 | 10 | # Adding your own recipe repository 11 | 12 | Just expose a directory with recipes, and add `index.txt` in that directory: 13 | 14 | ```bash 15 | $ find -type f | grep -v index.txt | sed 's/^\.\///g' > index.txt 16 | ``` 17 | 18 | Then add the index-url to the `podi` script: 19 | 20 | ```bash 21 | RECIPE_REPOS="https://raw.githubusercontent.com/coderofsalvation/podi/master/recipe/.index.txt https://yoururl/.index.txt" 22 | ``` 23 | 24 | > Profit! 25 | -------------------------------------------------------------------------------- /doc/systemd.md: -------------------------------------------------------------------------------- 1 | basically `/podi start` is to be called some way or another 2 | 3 | ## systemd example 4 | 5 | Many servers use systemd, so you can modify the text below and put it into `/etc/systemd/system/your-container.service`: 6 | 7 | ``` 8 | [Unit] 9 | Description=your-container 10 | Wants=network-online.target 11 | After=network-online.target 12 | 13 | [Service] 14 | Type=oneshot 15 | User=username 16 | Group=username 17 | RemainAfterExit=yes 18 | WorkingDirectory=/home/username/repository 19 | ExecStart=/home/username/repository/podi start 20 | ExecStop=/home/username/repository/podi stop 21 | Type=forking 22 | 23 | [Install] 24 | WantedBy=multi-user.target default.target 25 | ``` 26 | 27 | and then run: 28 | 29 | ``` 30 | $ systemctl daemon-reload 31 | $ systemctl start your-container 32 | $ systemctl status your-container 33 | (edit the service-file if it didn't work, and retry the 3 lines above) 34 | $ systemctl enable your-container 35 | ``` 36 | 37 | > voila..now it will persist across reboots 38 | 39 | # Example: boot all podis on boot 40 | 41 | ```shell 42 | $ cd /root 43 | $ ./startpods install 44 | Created symlink /etc/systemd/system/multi-user.target.wants/startpods.service → /lib/systemd/system/startpods.service. 45 | start now? (y/n) 46 | ``` 47 | 48 | download [the script here](https://gist.github.com/coderofsalvation/d101f0b8b60d1778989866fd4546de76) 49 | -------------------------------------------------------------------------------- /doc/workflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderofsalvation/podi/d87be4a300de9a05fd9d5ee65eccce381ace6da7/doc/workflow.jpg -------------------------------------------------------------------------------- /podi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | required="awk ssh git hostname basename" 4 | test -z $HOST && host=$USER@$(hostname) || host=$HOST 5 | test -z "$RECIPE_REPOS" && RECIPE_REPOS="https://raw.githubusercontent.com/coderofsalvation/podi/master/recipe/index.txt" 6 | test -z $PODI_APP && export PODI_APP=$(basename $(pwd)) 7 | #trap "trigger cleanup" 0 1 2 3 6 8 | C_GREY="\\033[1;30m" 9 | C_BOLD="\\033[1;37;40m" 10 | C_NORMAL="\\033[0;0m" 11 | C_CYAN="\\033[1;36m" 12 | C_PURPLE="\\033[38;5;207m" 13 | C_RED="\\033[0;31m" 14 | 15 | # some hipster wrappers to make things readable and sexy later on 16 | try() { set +e; "$@"; set -e; return 0; } 17 | silent() { "$@" 1>/dev/null 2>/dev/null; return $?; } 18 | installed() { which $1 2>/dev/null 1>/dev/null; } 19 | verbose() { printf " $C_BOLD\$$C_NORMAL $*\n"; "$@"; } 20 | prompt() { printf " [?] $C_CYAN$1$C_NORMAL"; printf "\n$2\n $3> "; read answer; } 21 | error() { printf " [$C_RED"e"$C_NORMAL] %s\n" "$*"; exit 1; } 22 | print() { printf " $C_PURPLE│ $C_NORMAL %s\n" "$*"; return 0; } 23 | soften() { cat | while IFS='' read line; do printf " $C_PURPLE| $C_GREY%s$C_NORMAL\n" "$line"; done; } 24 | header() { h=$1; shift; printf " $C_PURPLE├─ $C_CYAN""%s""$C_PURPLE $C_NORMAL%s\n" "$h" "$*"; } 25 | evalfunc() { type $1 | awk 'NR>2 && /^[^{][^}]/ { print $0 }'; } 26 | on() { export on_$1="$2 $(eval echo \$on_$1)"; } 27 | init_dirs() { test -d .git || git init; test -d .pod || mkdir .pod; } 28 | foreach() { local err=0; local args="$1"; 29 | shift; 30 | for j in $args; do "$@" "$j" || err=1; done 31 | test $err = 1 && return 1 || return 0 32 | } 33 | 34 | trigger() { printf "$C_NORMAL [$C_CYAN✓$C_NORMAL] $C_BOLD%s$C_NORMAL\n" "$1" 35 | local cmd=$1; shift 36 | local actions="$(eval echo \$on_$cmd)" 37 | set +e 38 | if test -n "$actions"; then 39 | silent try type $cmd && { $cmd "$@"; } 40 | for it in $actions; do trigger $it "$@"; done 41 | else 42 | silent type $cmd || return 0; 43 | silent type $cmd && $cmd "$@"; 44 | fi 45 | set -e 46 | return $? 47 | } 48 | 49 | init_post_receive(){ 50 | echo "#!/bin/sh 51 | export PODI_GITPUSH=1 52 | export PODI_REMOTE=$2 53 | export PODI_USER=$3 54 | export PODI_PORT=$4 55 | export PODI_BRANCH=$4 56 | export PODI_APP=$(basename $(pwd)) 57 | export PODI_COMMIT=\$(git --work-tree=$1 --git-dir=$1/.git log -n1 --pretty=format:\"%h\") 58 | cd $1 59 | mkdir .tmp 60 | git --work-tree=$1/.tmp --git-dir=$1/.git checkout -f $PODI_BRANCH 61 | test -f .tmp/podi && { 62 | test -f podi && rm podi 63 | test -d .pod && rm -rf .pod 64 | cp -r .tmp/podi .tmp/.pod . 65 | } 66 | rm -rf .tmp 67 | test -f podi && ./podi deploy 68 | " | awk '{ gsub("^[ ]+","",$0); print $0 }' 69 | } 70 | 71 | loadremote(){ 72 | cfg=.pod/$1/$2/config 73 | test -f $cfg || error "remote (.pod/$1/$2/config) does not exist" 74 | . $cfg 75 | } 76 | 77 | hello(){ 78 | echo -e ' 79 | \e[38;5;57m 88 88 80 | \e[38;5;93m 88 81 | \e[38;5;129m 8b,dPPYba, ,adPPYba, ,adPPYb,88 88 82 | \e[38;5;165m 88P` "8a a8" "8a a8" `Y88 88 83 | \e[0m 88 d8 8b d8 8b 88 88 84 | \e[38;5;201m 88b, ,a8" "8a, ,a8" "8a, ,d88 88 85 | \e[38;5;201m 88`YbbdP"` `"YbbdP"` `"8bbdP"Y8 88 86 | \e[38;5;205m 88 87 | \e[0m' 88 | } 89 | 90 | init(){ # init git@server:/dir/to/deploy [branch] [port] [name] : initializes a deployment 91 | echo "$1" | silent grep ":" || usage 92 | test -z $1 && usage 93 | trigger init_localhost 94 | trigger init_server "$@" 95 | } 96 | 97 | init_localhost(){ 98 | init_dirs 99 | test -f .pod/pipeline || { 100 | test -d .pod || mkdir -p .pod 101 | test -f .pod/pipeline || recipe pipeline 102 | test -d .pod/run || { 103 | prompt "how to run this app?" "$(recipe | grep '^run/' | cat -n)" 104 | runtime=$(recipe | grep '^run/' | awk "NR == $answer { print \$0 }") 105 | silent recipe $runtime && . .pod/$runtime 106 | init_runtime 107 | } 108 | test -d .pod && for i in .pod/*; do . $i; done 109 | test -f podi || cp $0 . 110 | } 111 | } 112 | 113 | # [branch] [port] 114 | init_server(){ 115 | user=$( echo $1 | awk '{ gsub("@.*","",$0); print $0 }') 116 | server=$( echo $1 | awk '{ gsub(".*@","",$0); gsub(":.*","",$0); print $0 }') 117 | gitpath=$( echo $1 | awk '{ gsub(".*:","",$0); print $0 }') 118 | appname=$(basename $gitpath) 119 | test -z $2 && branch=$(git branch | awk '/^\*/ { print $2 }') || branch=$2 120 | test -z $3 && port=22 || port=$3 121 | mkdir -p .pod/$user@$server 122 | local config=.pod/$user@$server/$appname/config 123 | mkdir -p $(dirname $config) 124 | echo "export server='$server' " > $config 125 | echo "export port='$port' " >> $config 126 | echo "export user='$user' " >> $config 127 | echo "export gitpath='$gitpath' " >> $config 128 | echo "export appname='$appname' " >> $config 129 | echo "export branch='$branch' " >> $config 130 | . $config 131 | try silent ssh -p $port $user@$server HOST=$user@$server mkdir $gitpath 132 | scp -r -P $port $0 .pod $user@$server:$gitpath/. 1>/dev/null 133 | try ssh -p $port $user@$server "cd $gitpath; ./podi init_gitops $gitpath $server $user $port $branch" 134 | set +e 135 | init_dirs 136 | init_remotes 137 | silent git push $appname $branch 138 | print "you can now run: git push $appname $branch" 139 | export PODI_SERVER=$server 140 | export PODI_BRANCH=$branch 141 | } 142 | 143 | # 144 | init_gitops(){ 145 | git --version 1>/dev/null 2>/dev/null || error please install git on $server 146 | test -d $1 || mkdir $1 147 | test -d $1/.git && printf "already initialized: $C_GREY$1/.git$C_NORMAL\n" 148 | test -d $1/.git || { 149 | silent git init --bare "$1/.git" || error could not create $1/.git 150 | } 151 | init_post_receive $1 $2 $3 $4 $5 > $1/.git/hooks/post-receive 152 | chmod +x $1/.git/hooks/post-receive 153 | } 154 | 155 | recipe(){ # recipe : installs a recipe from podi repo or url 156 | list(){ 157 | for repo in $RECIPE_REPOS; do 158 | print $(dirname $repo) 159 | curl -s $repo 160 | done 161 | } 162 | install(){ 163 | init_dirs 164 | for repo in $RECIPE_REPOS; do 165 | curl -s $repo | silent grep -E "^$1$" && { 166 | print "writing .pod/$1" 167 | dir=$(dirname $1) 168 | test -d .pod/$dir || mkdir -p .pod/$dir 169 | curl -s $(dirname $repo)/$1 > .pod/$dir/$(basename $1) 170 | } 171 | done 172 | } 173 | test -z $1 && list 174 | test -z $1 || install $1 175 | } 176 | 177 | init_remotes(){ 178 | find .pod | silent grep config || return 0 179 | for config in .pod/*@*/*/config; do 180 | . $config 181 | git remote | silent grep $appname || { 182 | header "adding git remote: $appname" 183 | git remote add -t $branch $appname ssh://$user@$server:$port$gitpath/.git 184 | } 185 | done 186 | } 187 | 188 | usage(){ 189 | init_dirs 190 | test -d .git && silent grep "bare = true" .git/config && { echo "Usage: ./podi start\n./podi stop"; exit 0; } 191 | echo "usage: " 192 | awk '/[a-zA-Z0-9_]+\(\){ #/ { 193 | info=$0 194 | gsub(".* : ","",info) 195 | gsub(".*{ # ","",$0) 196 | gsub(" :.*","",$0) 197 | printf(" %-55s %s\n",$0,info) 198 | }' $0 $(find .pod -type f | awk '{ printf "%s ",$0 }') 2>/dev/null 199 | echo 200 | init_remotes 201 | printf "deploy targets:\n" 202 | git remote | awk '{ printf(" %s\n",$1) }' 203 | exit 0 204 | } 205 | 206 | foreach "$required" installed || error "please install: $required" 207 | # source external variables, functions and decorators 208 | test -d .pod && for i in $(find .pod -type f); do . $i; done 209 | test -z $1 && usage 210 | trigger "$@" 211 | -------------------------------------------------------------------------------- /recipe/app/container/redbean: -------------------------------------------------------------------------------- 1 | 2 | create_container_app(){ 3 | header .pod/app/container 4 | test -f Dockerfile || { 5 | print "writing Dockerfile" 6 | echo "FROM docker.io/coderofsalvation/redbean" > Dockerfile 7 | } 8 | test -f .env || { 9 | print "writing .env" 10 | PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }') 11 | echo 'export PORT='$PORT >> .env 12 | } 13 | test -f app || { 14 | print "writing app" 15 | echo '#!/bin/sh' > app 16 | echo 'bg=-d' >> app 17 | echo 'container=$(which podman || which docker)' >> app 18 | echo 'test -z $PODI_APP && { bg=""; PODI_APP=$(basename $(pwd)); }' >> app 19 | echo 'set -x' >> app 20 | echo '$container run $bg --rm --volume $(pwd):/home/app -w /home/app -e PORT -p $PORT:80 --name $PODI_APP $PODI_APP /redbean.com -p 80 -vv' >> app 21 | echo 'set +x' >> app 22 | echo '$container logs $PODI_APP' >> app 23 | chmod 755 app 24 | } 25 | print "run './podi start' to run your container locally" 26 | } 27 | 28 | on init_localhost create_container_app 29 | -------------------------------------------------------------------------------- /recipe/checkout/rollback_simple: -------------------------------------------------------------------------------- 1 | # info: perform easy server-rollbacks based on gitcommit 2 | # installation: run 'podi recipe rollback.simple' or put this file into .pod folder 3 | # example usage: ./podman rollback git@server 3fe2f615 4 | 5 | checkout(){ 6 | header .pod/extract/rollback_simple 7 | test -z $ROLLBACK && { 8 | git --work-tree=$(pwd) --git-dir=$(pwd)/.git checkout -f 9 | } 10 | test -z $ROLLBACK || trigger do_rollback 11 | } 12 | 13 | do_rollback(){ 14 | git --work-tree=$(pwd) --git-dir=$(pwd)/.git reset $ROLLBACK --hard 15 | } 16 | 17 | rollback(){ # rollback : rolls back deployment to certain commit 18 | loadremote $1 $2 19 | scp -P $port $0 $user@$server:/tmp/. 1>/dev/null 20 | ssh -p $port $user@$server "export HOST=$user@$server; export ROLLBACK=$3; cd $gitpath; ./podi deploy" 21 | } 22 | -------------------------------------------------------------------------------- /recipe/hello: -------------------------------------------------------------------------------- 1 | # info: a barebones recipe (startingpoint for own recipe) 2 | 3 | hello(){ header myrecipe; } 4 | world(){ print helloworld; } 5 | 6 | mycmd(){ # mycmd [myopt] : simple example cmd 7 | echo "howdy! $1" 8 | } 9 | 10 | on hello world 11 | -------------------------------------------------------------------------------- /recipe/helloworld.yaml: -------------------------------------------------------------------------------- 1 | app: |- 2 | #!/bin/sh 3 | echo "PID=$(cat .pid) => $(date) PORT=$PORT" 4 | while sleep 1; do printf .; done 5 | 6 | foo: |- 7 | bar 8 | flop 9 | 10 | cmd: 11 | - chmod 755 app 12 | - echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" > .env 13 | -------------------------------------------------------------------------------- /recipe/index.txt: -------------------------------------------------------------------------------- 1 | helloworld.yaml 2 | hello 3 | pipeline 4 | app/container/redbean 5 | checkout/rollback_simple 6 | init/server/sshkey 7 | init/gitops/jail 8 | start/envfile 9 | run/baremetal_autosuspend 10 | run/container 11 | run/container_autosuspend 12 | run/baremetal_webcli 13 | run/baremetal 14 | run/container_webcli 15 | run/container_compose 16 | -------------------------------------------------------------------------------- /recipe/init/gitops/jail: -------------------------------------------------------------------------------- 1 | # info: runs app/ssh-sessions in oci container using docker or podman 2 | # installation : run 'podi recipe env-file' or put this file into .podi folder 3 | # example usage: * put an .env file in your repo (with 'export FOO=1' e.g.) 4 | # * [IMAGE=alpine] ./podi init git@server:/dir/to/deploy [branch] [port] [name] 5 | # * automatic ssh-into-container (docker/podman) is now enabled 6 | 7 | jail(){ 8 | header .pod/start/containerize 9 | print "initing automatic ssh-into-container" 10 | print "writing ~/.profile" 11 | install(){ 12 | script=' 13 | u=$(whoami) 14 | test -z $JAIL_IMAGE && JAIL_IMAGE=busybox 15 | test $u = "root" || { 16 | oci=$(which podman || which docker) 17 | $oci run -it -v /home/$u:/home/$u -w /home/$u $JAIL_IMAGE /bin/sh 18 | exit 19 | } 20 | ' 21 | echo "$script" | awk '{$1=$1};1' 22 | } 23 | install > ~/.profile 24 | } 25 | 26 | -------------------------------------------------------------------------------- /recipe/init/server/sshkey: -------------------------------------------------------------------------------- 1 | # info: generates sshkey to be shared with external developers 2 | # installation : run 'podi recipe sshkey' or put this file into .podman folder 3 | # example usage: ./podman init git@yourserver 4 | 5 | init_sshkey(){ 6 | header .pod/init/server/sshkey 7 | local key=~/.ssh/id_rsa_$appname 8 | test -f $key && { 9 | ssh-keygen -t rsa -N "" -f $key 10 | ssh-key-copy -p $port -i $key $user@$server 11 | } 12 | print "Yay! share key '$server:~/.ssh/id_rsa_$appname' to collaborate with devs " 13 | } 14 | 15 | on init_server init_sshkey 16 | -------------------------------------------------------------------------------- /recipe/pipeline: -------------------------------------------------------------------------------- 1 | deploy(){ 2 | trigger hello 3 | trigger backup 4 | trigger checkout 5 | trigger build 6 | trigger runtests 7 | trigger stop 8 | trigger start 9 | } 10 | 11 | checkout(){ 12 | git --work-tree=$(pwd) --git-dir=$(pwd)/.git checkout -f $PODI_BRANCH 13 | } 14 | -------------------------------------------------------------------------------- /recipe/run/baremetal: -------------------------------------------------------------------------------- 1 | # info: baremetal template, perfect to run background services on lowend systems 2 | 3 | daemonize(){ 4 | kill -0 $(cat .pid) 5 | while sleep 1s; do 6 | set +e 7 | ./app 8 | echo "$(tail -n200 app.log)" > app.log 9 | done 10 | } 11 | 12 | stop(){ 13 | test -f .pid && { print "stopping $PODI_APP"; silent try kill -15 $(cat .pid); } 14 | } 15 | 16 | start(){ 17 | nohup ./podi daemonize $PODI_APP:$PORT &> app.log & 18 | echo $! > .pid 19 | print "started $PODI_APP [PID $(cat .pid)] on $PODI_REMOTE:$PORT" 20 | } 21 | 22 | init_runtime(){ 23 | test -f app || { 24 | generate(){ 25 | echo '#!/bin/sh' 26 | echo 'echo PID=$(cat .pid) => $(date) PORT=$PORT' 27 | echo 'while sleep 1; do printf .; done ' 28 | } 29 | prompt "generate + commit 'app' startfile?" "$(generate | soften)" "[y/n] " 30 | test $answer = "y" || error aborting 31 | generate > app 32 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 33 | chmod 755 app 34 | git add app hello.txt .env .pod podi && git commit -m "adding podi" 35 | } 36 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 37 | test -f .pod/start/envfile || recipe start/envfile 38 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 39 | } 40 | -------------------------------------------------------------------------------- /recipe/run/baremetal_autosuspend: -------------------------------------------------------------------------------- 1 | # info: baremetal template, perfect for port-activated services on lowend systems 2 | 3 | daemonize(){ 4 | while sleep 0.2s; do 5 | set +e 6 | echo "starting $PODI_APP TTL=$TTL BOOTTIME=$BOOTTIME" 7 | TTLARG=$TTL 8 | nc 2>&1 | silent grep BusyBox || NCARG="-q1" 9 | timeout 2>&1 | silent grep BusyBox && TTLARG="-t $TTL" 10 | timeout -s 15 ${TTLARG} ./app 11 | echo "$(tail -n200 app.log)" > app.log 12 | echo sleeping on port $PORT 13 | echo "HTTP/1.1 302 OK\nrefresh:$BOOTTIME;url=/\n\n" | nc $NCARG -lp $PORT 14 | done 15 | } 16 | 17 | stop(){ 18 | test -f .pid && { 19 | print "stopping $PODI_APP"; 20 | silent try kill -15 $(cat .pid); 21 | } 22 | silent lsof -ti tcp:$PORT && lsof -ti tcp:$PORT | xargs kill 23 | } 24 | 25 | start(){ 26 | nohup ./podi daemonize $PODI_APP:$PORT &> app.log & 27 | echo $! > .pid 28 | print "started $PODI_APP [PID $(cat .pid)] on $PODI_REMOTE:$PORT" 29 | print "autosleeping after $TTL seconds (.env)" 30 | print "waking up after network request on port $PORT" 31 | } 32 | 33 | init_runtime(){ 34 | test -f app || { 35 | generate(){ 36 | echo '#!/bin/sh' 37 | echo 'echo PID=$(cat .pid) => $(date) PORT=$PORT' 38 | echo 'while sleep 1; do printf .; done ' 39 | } 40 | prompt "generate + commit 'app' startfile?" "$(generate | soften)" "[y/n] " 41 | test $answer = "y" || error aborting 42 | generate > app 43 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 44 | chmod 755 app 45 | git add app .env .pod podi && git commit -m "adding podi" 46 | } 47 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 48 | test -f .pod/start/envfile || recipe start/envfile 49 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 50 | echo "export TTL=5 # suspend after 5 seconds of activity (update me!)" >> .env 51 | echo "export BOOTTIME=1 # give app 1 second to wakeup (update me!)" >> .env 52 | } 53 | -------------------------------------------------------------------------------- /recipe/run/baremetal_webcli: -------------------------------------------------------------------------------- 1 | # info: baremetal cli-template, perfect for outputting terminal-cmds to web 2 | 3 | daemonize(){ 4 | while sleep 1s; do 5 | set +e 6 | ./app 7 | echo "$(tail -n200 app.log)" > app.log 8 | done 9 | } 10 | 11 | stop(){ 12 | test -f .pid && { print "stopping $PODI_APP"; silent try kill -15 $(cat .pid); } 13 | } 14 | 15 | start(){ 16 | eval "$(cat .env)" 17 | { nohup ./podi daemonize $PODI_APP:$PORT 1> app.log 2> app.log; } & 18 | echo $! > .pid 19 | print "started $PODI_APP [PID $(cat .pid)] on $PODI_REMOTE:$PORT" 20 | print "just try 'curl $PODI_REMOTE:$PORT' or use your browser" 21 | } 22 | 23 | init_runtime(){ 24 | test -f app || { 25 | generate(){ 26 | echo '#!/bin/sh' 27 | echo 'export JOB="cat hello.txt ; ls --color -la"' 28 | echo 'which socat &>/dev/null || { echo "please install socat as root"; exit 1; }' 29 | echo 'socat -t2 TCP4-LISTEN:$PORT,fork,max-children=3,forever,reuseaddr SYSTEM:"$JOB",pty,echo=0;' 30 | } 31 | generate > app 32 | chmod 755 app 33 | echo "HTTP/1.1 200\n\n H E L L O " > hello.txt 34 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 35 | git add app hello.txt .env .pod podi && git commit -m "adding podi" 36 | } 37 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 38 | test -f .pod/start/envfile || recipe start/envfile 39 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 40 | } 41 | -------------------------------------------------------------------------------- /recipe/run/container: -------------------------------------------------------------------------------- 1 | # info: containerized app, nice starting point for Dockerfile + app 2 | export POD=$(which podman || which docker || echo "") 3 | 4 | hint_systemd(){ 5 | service=/etc/systemd/system/$PODI_APP.service 6 | silent which podman || return 0 7 | test -f $service && return 0 8 | try podman generate systemd $PODI_APP 2>/dev/null > $PODI_APP.service 9 | print "to survive server-reboots please run as root:" 10 | echo "podman generate systemd $PODI_APP $service" | soften 11 | echo "systemctl enable $PODI_APP.service" | soften 12 | } 13 | 14 | build(){ 15 | header .pod/app/container 16 | test -f Dockerfile || { print "'Dockerfile' not found..skipping build"; } 17 | test -f Dockerfile && { 18 | silent which $POD || print '[!] please install podman (or docker)' 19 | silent which $POD && verbose $POD build -t $PODI_APP . 20 | } | soften 21 | return 0 22 | } 23 | 24 | stop(){ 25 | test -z $POD || { 26 | silent try $POD kill $PODI_APP 27 | silent try $POD rm -f $PODI_APP 28 | } 29 | } 30 | 31 | start(){ 32 | header .pod/app/container 33 | silent which $POD || error "please install podman (or docker)" 34 | test -z $POD || { 35 | export PODI_APP=$PODI_APP 36 | eval "$(cat .env)" 37 | verbose ./app 38 | hint_systemd 39 | print "" 40 | print "your container is running at $PODI_REMOTE:$PORT" 41 | } 42 | return 0 43 | } 44 | 45 | init_runtime(){ 46 | test -f app || { 47 | generate(){ 48 | echo '#!/bin/sh' 49 | echo 'set -x' 50 | echo 'POD=$(which podman || which docker)' 51 | echo 'test -z $PODI_APP && export PODI_APP=$(basename $(pwd))' 52 | echo 53 | echo '$POD run -d --rm --volume $(pwd):/app -w /app -e PORT -p $PORT:8080 --name $PODI_APP $PODI_APP /redbean.com' 54 | echo '$POD logs $PODI_APP' 55 | } 56 | prompt "generate + commit 'app' startfile?" "$(generate | soften)" "[y/n] " 57 | test $answer = "y" || error aborting 58 | echo "FROM docker.io/coderofsalvation/redbean:1.5" > Dockerfile 59 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 60 | generate > app 61 | chmod 755 app 62 | git add app Dockerfile .env .pod podi && git commit -m "adding podi" 63 | } 64 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 65 | test -f .pod/start/envfile || recipe start/envfile 66 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 67 | } 68 | -------------------------------------------------------------------------------- /recipe/run/container_autosuspend: -------------------------------------------------------------------------------- 1 | # info: containerized port-activated app, nice starting point for Dockerfile + app 2 | 3 | export POD=$(which podman || which docker || echo "") 4 | 5 | hint_systemd(){ 6 | service=/etc/systemd/system/$PODI_APP.service 7 | silent which podman || return 0 8 | test -f $service && return 0 9 | try podman generate systemd $PODI_APP 2>/dev/null > $PODI_APP.service 10 | print "to survive server-reboots please run as root:" 11 | echo "podman generate systemd $PODI_APP $service" | soften 12 | echo "systemctl enable $PODI_APP.service" | soften 13 | } 14 | 15 | build(){ 16 | header .pod/app/container 17 | test -f Dockerfile || { print "'Dockerfile' not found..skipping build"; } 18 | test -f Dockerfile && { 19 | silent which $POD || print '[!] please install podman (or docker)' 20 | silent which $POD && verbose $POD build -t $PODI_APP . 21 | } | soften 22 | return 0 23 | } 24 | 25 | daemonize(){ 26 | while sleep 0.2s; do 27 | set +e 28 | POD=$(which podman || which docker) 29 | nc 2>&1 | silent grep BusyBox || NCARG="-q1" 30 | echo "starting $PODI_APP TTL=$TTL BOOTTIME=$BOOTTIME" 31 | test -z $($POD ps -a --filter=name=$PODI_APP| head -n-1) && ./app 32 | $POD start $PODI_APP 33 | sleep $TTL 34 | $POD stop $PODI_APP 35 | echo sleeping on port $PORT 36 | echo "HTTP/1.1 302 OK\nrefresh:$BOOTTIME;url=/\n\n" | nc $NCARG -lp $PORT 37 | done 38 | } 39 | 40 | stop(){ 41 | test -f .pid && { 42 | print "stopping $PODI_APP"; 43 | silent try kill -15 $(cat .pid); 44 | } 45 | test -z $POD || { 46 | silent try $POD kill $PODI_APP 47 | silent try $POD rm -f $PODI_APP 48 | } 49 | silent lsof -ti tcp:$PORT && lsof -ti tcp:$PORT | xargs kill 50 | } 51 | 52 | start(){ 53 | header .pod/app/container 54 | POD=$(which podman || which docker || echo "") 55 | silent which $POD || error "please install podman (or docker)" 56 | test -z $POD || { 57 | export PODI_APP=$PODI_APP 58 | export PODI_GITPUSH=$PODI_GITPUSH 59 | eval "$(cat .env)" 60 | { nohup ./podi daemonize $PODI_APP:$PORT 1> app.log 2> app.log; } & 61 | echo $! > .pid 62 | hint_systemd 63 | print "" 64 | print "started container $PODI_APP [PID $(cat .pid)] on $PODI_REMOTE:$PORT" 65 | print "autosleeping after $TTL seconds (.env)" 66 | print "waking up after network request on port $PORT" 67 | } 68 | return 0 69 | } 70 | 71 | init_runtime(){ 72 | test -f app || { 73 | generate(){ 74 | echo '#!/bin/sh' 75 | echo 'set -x' 76 | echo 'POD=$(which podman || which docker)' 77 | echo 'test -z $PODI_APP && export PODI_APP=$(basename $(pwd))' 78 | echo 79 | echo '$POD run -d --rm --volume $(pwd):/app -w /app -e PORT -p $PORT:8080 --name $PODI_APP $PODI_APP /redbean.com' 80 | echo '$POD logs $PODI_APP' 81 | } 82 | prompt "generate + commit 'app' startfile?" "$(generate | soften)" "[y/n] " 83 | test $answer = "y" || error aborting 84 | echo "FROM docker.io/coderofsalvation/redbean:1.5" > Dockerfile 85 | generate > app 86 | chmod 755 app 87 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 88 | git add app Dockerfile .env .pod podi && git commit -m "adding podi" 89 | } 90 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 91 | test -f .pod/start/envfile || recipe start/envfile 92 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 93 | echo "export TTL=5 # suspend after 5 seconds of activity (update me!)" >> .env 94 | echo "export BOOTTIME=2 # give app 2 second to wakeup (update me!)" >> .env 95 | } 96 | -------------------------------------------------------------------------------- /recipe/run/container_compose: -------------------------------------------------------------------------------- 1 | # info: containerized stack, nice startingpoint using docker-compose.yml 2 | 3 | export POD=$(which podman-compose || which docker-compose) 4 | 5 | build(){ 6 | header .pod/app/container-compose 7 | test -f docker-compose.yml || { print "'docker-compose.yml' not found..skipping build"; } 8 | test -f docker-compose.yml && { 9 | silent which $POD || print '[!] please install podman-compose (or docker-compose)' 10 | silent which $POD && verbose $POD build 11 | } | soften 12 | return 0 13 | } 14 | 15 | stop(){ 16 | test -z $POD || verbose $POD down -t 2 2>/dev/null 17 | } 18 | 19 | start(){ 20 | header .pod/app/container 21 | silent which $POD || error "please install podman-compose (or docker-compose)" 22 | test -z $POD || { 23 | export PODI_APP=$PODI_APP 24 | eval "$(cat .env)" 25 | verbose $POD up -d --force-recreate 26 | verbose $POD logs 27 | print "" 28 | print "your container(s) is running at $PODI_REMOTE:$PORT" 29 | } 30 | return 0 31 | } 32 | 33 | init_runtime(){ 34 | test -f docker-compose.yml || { 35 | generate(){ 36 | echo 'version: "3.9"' 37 | echo 'services:' 38 | echo ' web:' 39 | echo ' image: docker.io/coderofsalvation/redbean:1.5' 40 | echo ' command: /redbean.com -D /app' 41 | echo ' volumes:' 42 | echo ' - .:/app' 43 | echo ' ports:' 44 | echo ' - "${PORT}:8080"' 45 | } 46 | PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }') 47 | prompt "generate + commit 'docker-compose.yml' file?" "$(generate | soften)" "[y/n] " 48 | generate > docker-compose.yml 49 | test $answer = "y" || error aborting 50 | echo "export PORT=$PORT" >> .env 51 | git add docker-compose.yml .env .pod podi && git commit -m "adding podi" 52 | } 53 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 54 | test -f .pod/start/envfile || recipe start/envfile 55 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 56 | } 57 | -------------------------------------------------------------------------------- /recipe/run/container_webcli: -------------------------------------------------------------------------------- 1 | # info: containerized cli-template, perfect for outputting terminal-cmds to web 2 | 3 | export POD=$(which podman || which docker || echo "") 4 | 5 | hint_systemd(){ 6 | service=/etc/systemd/system/$PODI_APP.service 7 | silent which podman || return 0 8 | test -f $service && return 0 9 | try podman generate systemd $PODI_APP 2>/dev/null > $PODI_APP.service 10 | print "to survive server-reboots please run as root:" 11 | echo "podman generate systemd $PODI_APP $service" | soften 12 | echo "systemctl enable $PODI_APP.service" | soften 13 | } 14 | 15 | build(){ 16 | header .pod/app/container_webcli 17 | test -f Dockerfile || { print "'Dockerfile' not found..skipping build"; } 18 | test -f Dockerfile && { 19 | silent which $POD || print '[!] please install podman (or docker)' 20 | silent which $POD && verbose $POD build -t $PODI_APP . 21 | } | soften 22 | return 0 23 | } 24 | 25 | stop(){ 26 | header .pod/app/container_webcli 27 | test -z $POD || { 28 | silent try $POD kill $PODI_APP 29 | silent try $POD rm -f $PODI_APP 30 | } 31 | } 32 | 33 | start(){ 34 | header .pod/app/container_webcli 35 | test -z $PODI_APP && PODI_APP=$(basename $(pwd)) 36 | silent test -z $POD && error "please install podman (or docker)" 37 | test -z $POD || { 38 | export PODI_APP=$PODI_APP 39 | eval "$(cat .env)" 40 | POD=$(which podman || which docker) 41 | verbose $POD run -d --volume $(pwd):/home/app -w /home/app -e PORT -p $PORT:$PORT --name $PODI_APP --entrypoint=sh $PODI_APP -c ./app 42 | verbose $POD logs $PODI_APP 2>&1 | awk "{print \"[app.log] \"\$0 }" 43 | hint_systemd 44 | print "" 45 | print "your container is running at $PODI_REMOTE:$PORT" 46 | } 47 | return 0 48 | } 49 | 50 | init_runtime(){ 51 | test -f app || { 52 | generate(){ 53 | echo '#!/bin/sh' 54 | echo 'export JOB="cat hello.txt ; ls -la"' 55 | echo 'which socat &>/dev/null || { echo "please install socat as root"; exit 1; }' 56 | echo 'socat -t2 TCP4-LISTEN:$PORT,fork,max-children=3,forever,reuseaddr SYSTEM:"$JOB",pty,echo=0;' 57 | } 58 | prompt "generate + commit 'app' startfile?" "$(generate | soften)" "[y/n] " 59 | test $answer = "y" || error aborting 60 | generate > app 61 | chmod 755 app 62 | echo "HTTP/1.1 200\n\n H E L L O " > hello.txt 63 | echo "FROM docker.io/alpine/socat" > Dockerfile 64 | echo "export PORT=$(awk 'BEGIN{ srand(); print int(rand()*1000)+8000 }')" >> .env 65 | git add app Dockerfile .env hello.txt .pod podi && git commit -m "adding podi" 66 | } 67 | test -f .pod/checkout/rollback_simple || recipe checkout/rollback_simple 68 | test -f .pod/start/envfile || recipe start/envfile 69 | test -f .pod/init/server/sshkey || recipe init/server/sshkey 70 | } 71 | -------------------------------------------------------------------------------- /recipe/start/envfile: -------------------------------------------------------------------------------- 1 | # info: sources environment variables from `.env` file during deployment 2 | # installation : run 'podi recipe env-file' or put this file into .podi folder 3 | # example usage: * put an .env file in your repo (with 'export FOO=1' e.g.) 4 | # * override them on remote-server using './podi .env git@server FOO=2' 5 | # * run './podi .env' to see all local/remote env-vars 6 | 7 | envfile(){ 8 | header .pod/envfile 9 | test -f .env && { print "reading '.env'" ; eval "$(cat .env)"; } 10 | test -f .env.live && { print "reading '.env.live'"; eval "$(cat .env.live)"; } 11 | } 12 | 13 | envset(){ # envset [git@server] [app] [FOO=bar] : shows or sets [remote] environment variables 14 | test -f .env || echo 'export FOO=bar # used for development' > .env 15 | if test -n "$3"; then 16 | loadremote $1 $2 17 | printf "export %-40s # $(date)\n" "$3\"" | awk '{ gsub("=","=\"",$0); print $0; }' | ssh -p $port $1 "cat >> $gitpath/.env.live" 18 | print OK 19 | else 20 | for app in .pod/*@*/*/config; do 21 | . $app 22 | print "$server:$appname (.env.live):" 23 | test -z $3 && ssh -p $port $user@$server "test -f $gitpath/.env.live && cat $gitpath/.env.live || echo ' no env-vars yet'" 24 | echo 25 | done 26 | fi 27 | } 28 | 29 | on build envfile 30 | on checkout envfile 31 | on init_runtime ensure_file_env 32 | --------------------------------------------------------------------------------