├── LICENSE
├── Makefile
├── README.md
├── assets
├── gif
│ ├── failover.gif
│ ├── multi-host-networking.gif
│ ├── quickstart.gif
│ ├── runscript-deploy.each.gif
│ ├── runscript-deploy.once.gif
│ ├── runscript-deploy.random.gif
│ ├── runscript-deploy.scale.1.gif
│ ├── runscript-deploy.scale.2.gif
│ └── runscript-deploy.single.gif
└── img
│ ├── Imagotype.png
│ ├── Isotype.png
│ ├── Logo.png
│ ├── Slogan.png
│ └── architecture.png
├── jet.sh
├── littlejet.sh
├── make.sh
└── share
├── littlejet
├── files
│ ├── cpignore
│ ├── default.conf
│ ├── lib.subr
│ └── user.conf
└── runscripts
│ ├── deploy.all
│ ├── deploy.all.seq
│ ├── deploy.each
│ ├── deploy.once
│ ├── deploy.random
│ ├── deploy.scale
│ ├── deploy.single
│ ├── vpn.wg.client
│ ├── vpn.wg.client.destroy
│ ├── vpn.wg.load-balancer.pen
│ ├── vpn.wg.load-balancer.pen.destroy
│ ├── vpn.wg.server
│ └── vpn.wg.server.destroy
└── man
├── man1
└── littlejet.1
└── man5
└── littlejet.conf.5
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2024, DtxdF
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | MKDIR?=mkdir -p
2 | INSTALL?=install
3 | SED?=sed -i ''
4 | RM?=rm
5 | PREFIX?=/usr/local
6 | FIND?=find
7 | MANDIR?=${PREFIX}/share/man
8 | MANPAGES=man1/littlejet.1 \
9 | man5/littlejet.conf.5
10 |
11 | LITTLEJET_VERSION?=0.2.0
12 |
13 | all: install
14 |
15 | install:
16 | ${MKDIR} -m 755 -p "${DESTDIR}${PREFIX}/bin"
17 | ${MKDIR} -m 755 -p "${DESTDIR}${PREFIX}/share"
18 | ${MKDIR} -m 755 -p "${DESTDIR}${PREFIX}/share/littlejet"
19 | ${MKDIR} -m 755 -p "${DESTDIR}${MANDIR}/man1"
20 | ${MKDIR} -m 755 -p "${DESTDIR}${MANDIR}/man5"
21 |
22 | ${INSTALL} -m 555 jet.sh "${DESTDIR}${PREFIX}/bin/jet"
23 | ${INSTALL} -m 555 littlejet.sh "${DESTDIR}${PREFIX}/bin/littlejet"
24 |
25 | # files
26 | ${MKDIR} -m 755 -p "${DESTDIR}${PREFIX}/share/littlejet/files"
27 | ${FIND} share/littlejet/files -mindepth 1 -exec ${INSTALL} -m 444 {} "${DESTDIR}${PREFIX}/{}" \;
28 |
29 | # RunScripts
30 | ${MKDIR} -m 755 -p "${DESTDIR}${PREFIX}/share/littlejet/runscripts"
31 | ${FIND} share/littlejet/runscripts -mindepth 1 -exec ${INSTALL} -m 555 {} "${DESTDIR}${PREFIX}/{}" \;
32 |
33 | # Version
34 | ${SED} -e 's|%%LITTLEJET_VERSION%%|${LITTLEJET_VERSION}|' "${DESTDIR}${PREFIX}/bin/jet"
35 |
36 | # man pages
37 | .for manpage in ${MANPAGES}
38 | ${INSTALL} -m 444 share/man/${manpage} "${DESTDIR}${MANDIR}/${manpage}"
39 | .endfor
40 |
41 | # Prefix
42 | .for f in share/littlejet/files/default.conf bin/jet bin/littlejet share/man/man1/littlejet.1 share/man/man5/littlejet.conf.5
43 | ${SED} -i '' -e 's|%%PREFIX%%|${PREFIX}|' "${DESTDIR}${PREFIX}/${f}"
44 | .endfor
45 |
46 | uninstall:
47 | ${RM} -f "${DESTDIR}${PREFIX}/bin/jet"
48 | ${RM} -rf "${DESTDIR}${PREFIX}/share/littlejet"
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ----
6 |
7 | # LittleJet - Create, deploy, manage and scale FreeBSD jails anywhere
8 |
9 | LittleJet is an open source, easy-to-use orchestrator for managing, deploying, scaling and interconnecting FreeBSD jails anywhere in the world.
10 |
11 | ## Quickstart
12 |
13 |
14 |
15 |
16 | Just a few commands and you will get deployed the project on all nodes.
17 |
18 |
19 | ```sh
20 | git clone https://github.com/DtxdF/hello-http
21 | cd ./hello-http/
22 | jet create hello
23 | jet add-node
24 | jet add-node # optional
25 | jet add-node # optional
26 | jet run-script -p hello deploy.all
27 | jet show hello
28 | jet run-appjail -Pp hello cmd jexec hello-http fetch -qo - http://localhost
29 | jet destroy hello
30 | ```
31 |
32 | ## How LittleJet works: architecture
33 |
34 |
35 |
36 |
37 | Sample architecture: Load balancing two web server replicas on nodes #1 and #2.
38 |
39 |
40 | Although it may be much more basic than the image above, showing all the toys is better to demonstrate what you can do with LittleJet.
41 |
42 | In the image above there are four nodes. The first and second nodes have a replica of the same web server, so they provide the same service. The fourth node provides the load balancing software. The question is: how does the load balancer on the fourth node send and receive packets to and from the first and second nodes? Easy: They are all connected to the same VPN server on the third node, but the difference is how they connect to that node. The first and second nodes use a jail called *connector* that has the VPN client and some packet filter rules configured to forward packets to the web server, so nodes on the same VPN can make HTTP requests. The load balancer itself has two pieces of software, the load balancer and the VPN client, and you only need to have all the connector's IP addresses to load balance them all.
43 |
44 | All of these pieces are created, configured and deployed using LittleJet with just a few commands from Manager, the host that can connect to all nodes in the cluster.
45 |
46 | ## What you can do with LittleJet: features
47 |
48 | ### Projects instead of jails
49 |
50 | Instead of simply dealing with jails, we exploit the concept of [Director](https://github.com/DtxdF/director) projects. A project is simply a group of jails and this is very useful because you can deploy one or more jails on the same node to take advantage of locality. Also, since there are many projects already created, you can simply copy them, edit them to suit your environment, and simply deploy them.
51 |
52 | ### RunScripts
53 |
54 | We can just strictly implement all the things in LittleJet, such as the connector, load balancer, deployment algorithms, etc. There is another way to implement those things: through RunScripts.
55 |
56 | A RunScript is a form of automation that LittleJet uses to perform more tasks than it was initially designed for. In this way, LittleJet is very modular and can be integrated with any other system. Even better: you don't need to write a RunScript in the POSIX shell, you can use the language of your choice, for example, Python, Golang, Rust, etc.
57 |
58 | Some RunScripts already implemented:
59 |
60 |
61 |
62 |
63 | deploy.random: Deploy a project to a randomly chosen node.
64 |
65 |
66 |
67 |
68 |
69 | deploy.once: Deploy a project to a node if it is not already deployed to any of them.
70 |
71 |
72 |
73 |
74 |
75 | deploy.each: For each run, deploy to any of the nodes.
76 |
77 |
78 |
79 |
80 |
81 | deploy.single: Deploy a project to the given node.
82 |
83 |
84 | ### Scaling
85 |
86 | An orchestrator that cannot automatically scale to other nodes is not that useful. LittleJet scales your project easily and effortlessly with —surprise— a RunScript.
87 |
88 |
89 |
90 |
91 | Deploying a project with a minimum of two replicas.
92 |
93 |
94 | Very simple, but in real life the web server will be overloaded, can LittleJet auto-scale the project using jail or project metrics?
95 |
96 |
97 |
98 |
99 | Yes!
100 |
101 |
102 | ### Load balancing / Failover / Multi-host networking
103 |
104 | You say that you have a replica of a web server on many nodes around the world, in several countries, but you want to access it using your favorite web browser on your laptop.
105 |
106 |
107 |
108 |
109 | Load balancing three replicas of a web server.
110 |
111 |
112 |
113 |
114 |
115 | Failover.
116 |
117 |
118 | ### And much more...
119 |
120 | LittleJet is very, very simple: it depends on the lower layers to do its job, i.e. it depends on [AppJail](https://github.com/DtxdF/AppJail), [Director](https://github.com/DtxdF/director), etc., so check out those projects to see what crazy combinations you can make.
121 |
122 | ## Dependencies
123 |
124 | ### Manager
125 |
126 | * [textproc/jq](https://freshports.org/textproc/jq)
127 | * [sysutils/cpdup](https://freshports.org/sysutils/cpdup)
128 | * [textproc/sansi](https://freshports.org/textproc/sansi)
129 |
130 | ### Nodes
131 |
132 | * [sysutils/cpdup](https://freshports.org/sysutils/cpdup)
133 | * [sysutils/py-director](https://freshports.org/sysutils/py-director)
134 | * [sysutils/appjail](https://freshports.org/sysutils/appjail) or [sysutils/appjail-devel](https://freshports.org/sysutils/appjail-devel)
135 |
136 | ## Documentation
137 |
138 | * [wiki](https://github.com/DtxdF/LittleJet/wiki)
139 | * `man 1 littlejet`
140 | * `man 5 littlejet.conf`
141 |
142 | ## Recommendations
143 |
144 | Configuring each node can be painful if there are a lot of nodes, so use a tool like Ansible or Puppet to suit your environment.
145 |
146 | ## Contributing
147 |
148 | Here is a list of some things you can contribute to LittleJet:
149 |
150 | * Report or fix bugs.
151 | * Create a new RunScript. You don't need to submit a PR to this repository, you can create your own repository and share it, so I can create a new section on the wiki called *"User RunScripts"*. Of course, if you want to send me a PR with your RunScript, I have no problem.
152 | * Contribute to projects this project depends on, such as [AppJail](https://github.com/DtxdF/AppJail) or [Director](https://github.com/DtxdF/director).
153 | * ...
154 |
155 | ## Notes
156 |
157 | 1. `BatchMode` is set to `yes`, which means, quoting an excerpt from `ssh_config(5)`, *“... user interaction, such as password prompt and host key confirmation prompts, will be disabled.“*
158 |
159 | If you have your SSH private key with a password, use `ssh-add(1)` and `ssh-agent(1)` before using LittleJet.
160 | 2. The `-t` parameter of `ssh(1)` is set, which means that if you want to process some text, you cannot do so because the text will be mangled. This note is when using one of the `run-*` subcommands. A simple workaround is the `-C` flag in one of the `run-*` subcommands that use sansi to remove such control characters.
161 | 4. If you installed Director using pipx, note that it cannot be used over SSH when installed in `~/.local/bin/appjail-director` because `~/.local/bin` is not yet in the PATH environment variable that is loaded by `~/.profile`:
162 |
163 | ```sh
164 | $ ssh which appjail-director
165 | $ echo $?
166 | 1
167 | $ ssh ls .local/bin/appjail-director
168 | .local/bin/appjail-director
169 | $ echo $?
170 | 0
171 | ```
172 |
173 | A simple workaround is to add the following script in your `/usr/local/bin`.
174 |
175 | Note that this is not necessary when installed using [sysutils/py-director](https://freshports.org/sysutils/py-director).
176 | 5. LittleJet is designed to run as a non-root user, but on the remote site, AppJail needs privileges. If you are not using root on the remote site, [configure AppJail to use a trusted user](https://appjail.readthedocs.io/en/latest/trusted-users/).
177 | 6. Do not put your volumes in the same directory as the project because they can be overwritten when redeploying or simply destroyed when destroying a project. Use an external directory on each node.
178 | 7. The remote user must use the `sh(1)` shell.
179 | 8. Allowed characters:
180 |
181 | - Labels: `^[a-z][a-z0-9]*((\.|-)?[a-z][a-z0-9]*)*$`
182 | - Nodes: `^[a-zA-Z0-9._@-]+'`
183 | - Projects: `^[a-zA-Z0-9._-]+$`
184 | 9. Keep in-sync AppJail, Director and LittleJet.
185 |
--------------------------------------------------------------------------------
/assets/gif/failover.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/failover.gif
--------------------------------------------------------------------------------
/assets/gif/multi-host-networking.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/multi-host-networking.gif
--------------------------------------------------------------------------------
/assets/gif/quickstart.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/quickstart.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.each.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.each.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.once.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.once.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.random.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.random.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.scale.1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.scale.1.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.scale.2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.scale.2.gif
--------------------------------------------------------------------------------
/assets/gif/runscript-deploy.single.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/gif/runscript-deploy.single.gif
--------------------------------------------------------------------------------
/assets/img/Imagotype.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/img/Imagotype.png
--------------------------------------------------------------------------------
/assets/img/Isotype.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/img/Isotype.png
--------------------------------------------------------------------------------
/assets/img/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/img/Logo.png
--------------------------------------------------------------------------------
/assets/img/Slogan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/img/Slogan.png
--------------------------------------------------------------------------------
/assets/img/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DtxdF/LittleJet/342e8abd015e84c0f9f39ce0fe4113fc8afdcbe5/assets/img/architecture.png
--------------------------------------------------------------------------------
/littlejet.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | . "%%PREFIX%%/bin/jet"
4 |
--------------------------------------------------------------------------------
/make.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Script designed to be run for development purposes only.
5 | #
6 |
7 | "${SUEXEC:-doas}" make LITTLEJET_VERSION=`make -V LITTLEJET_VERSION`+`git rev-parse HEAD`
8 |
--------------------------------------------------------------------------------
/share/littlejet/files/cpignore:
--------------------------------------------------------------------------------
1 | .git
2 |
--------------------------------------------------------------------------------
/share/littlejet/files/default.conf:
--------------------------------------------------------------------------------
1 | # Home directory.
2 | UID=`id -u`
3 | HOMEDIR=`getent passwd ${UID} | cut -d: -f6` || exit $?
4 |
5 | if [ ! -d "${HOMEDIR}" ]; then
6 | err "Cannot find home directory '${HOMEDIR}'"
7 | fi
8 |
9 | PREFIX="%%PREFIX%%"
10 | SHAREDIR="${PREFIX}/share/littlejet"
11 | FILESDIR="${SHAREDIR}/files"
12 | DATADIR="${PREFIX}/littlejet"
13 | LIB_SUBR="${FILESDIR}/lib.subr"
14 |
15 | LITTLEJETDIR="${HOMEDIR}/.littlejet"
16 | PROJECTSDIR="${LITTLEJETDIR}/projects"
17 | RUNSCRIPTS="${LITTLEJETDIR}/runscripts ${SHAREDIR}/runscripts"
18 | SOCKETSDIR="${LITTLEJETDIR}/sockets"
19 | NODESDIR="${LITTLEJETDIR}/nodes"
20 | CONTROLPATH="%r.%h.%p"
21 | CONTROLPERSIST="8m"
22 | CPIGNORE="${FILESDIR}/cpignore"
23 | DEBUG="NO"
24 | REMOTE_DATADIR="${DATADIR}"
25 | REMOTE_PROJECTSDIR="${REMOTE_DATADIR}/projects"
26 | NCPU=`sysctl -n hw.ncpu`
27 | SSH_LOGLEVEL="ERROR"
28 | SHOW_HEALTHCHECKERS=0
29 | SHOW_LIMITS=0
30 | SHOW_STATS=0
31 |
--------------------------------------------------------------------------------
/share/littlejet/files/lib.subr:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
3 | # All rights reserved.
4 | #
5 | # Redistribution and use in source and binary forms, with or without
6 | # modification, are permitted provided that the following conditions are met:
7 | #
8 | # * Redistributions of source code must retain the above copyright notice, this
9 | # list of conditions and the following disclaimer.
10 | #
11 | # * Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # * Neither the name of the copyright holder nor the names of its
16 | # contributors may be used to endorse or promote products derived from
17 | # this software without specific prior written permission.
18 | #
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
30 | set -T
31 |
32 | # Default name.
33 | NAME="main"
34 |
35 | # Colors.
36 | COLOR_DEFAULT="\033[39;49m"
37 | COLOR_RED="\033[0;31m"
38 | COLOR_LIGHT_YELLOW="\033[0;93m"
39 | COLOR_LIGHT_BLUE="\033[0;94m"
40 | COLOR_GRAY="\033[0;90m"
41 |
42 | # See sysexits(3).
43 | EX_OK=0
44 | EX_USAGE=64
45 | EX_DATAERR=65
46 | EX_NOINPUT=66
47 | EX_NOUSER=67
48 | EX_NOHOST=68
49 | EX_UNAVAILABLE=69
50 | EX_SOFTWARE=70
51 | EX_OSERR=71
52 | EX_OSFILE=72
53 | EX_CANTCREAT=73
54 | EX_IOERR=74
55 | EX_TEMPFAIL=75
56 | EX_PROTOCOL=76
57 | EX_NOPERM=77
58 | EX_CONFIG=78
59 |
60 | # usage:
61 | # setname
62 | # args:
63 | # : Module name.
64 | # description:
65 | # The name is displayed for each execution of logging functions, such as debug,
66 | # info, err, and warn.
67 | #
68 | setname()
69 | {
70 | NAME="$1"
71 | }
72 |
73 | # usage:
74 | # checkdependency
75 | # description:
76 | # Check if a program can be located using the PATH environment variable and if not
77 | # it exists with exit status EX_UNAVAILABLE.
78 | #
79 | checkdependency()
80 | {
81 | if ! which -s "$1"; then
82 | err "$1: dependency required but cannot be found."
83 | exit ${EX_UNAVAILABLE}
84 | fi
85 | }
86 |
87 | # usage:
88 | # checklabelname
89 | # description:
90 | # Check if a label is valid..
91 | #
92 | checklabelname()
93 | {
94 | if printf "%s" "$1" | grep -qEe '^[a-z][a-z0-9]*((\.|-)?[a-z][a-z0-9]*)*$'; then
95 | return 0
96 | else
97 | return 1
98 | fi
99 | }
100 |
101 | # usage:
102 | # checklabel
103 | # args:
104 | # : Project where the node is located to find the label.
105 | # : Node where the label can be located.
106 | # : Label name.
107 | # description:
108 | # Check if a label exists.
109 | #
110 | checklabel()
111 | {
112 | if [ $# -lt 3 ]; then
113 | err "usage: checklabel "
114 | exit ${EX_USAGE}
115 | fi
116 |
117 | local project
118 | project="$1"
119 |
120 | local node
121 | node="$2"
122 |
123 | local label
124 | label="$3"
125 |
126 | local labelfile
127 | labelfile="${NODESDIR}/${project}/${node}/labels/${label}"
128 |
129 | if [ -f "${labelfile}" ]; then
130 | return 0
131 | else
132 | return 1
133 | fi
134 | }
135 |
136 | # usage:
137 | # checklabelsyntax =
138 | # description:
139 | # Check if a label is valid if it matches a specific syntax, which includes its value.
140 | #
141 | checklabelsyntax()
142 | {
143 | if printf "%s" "$1" | grep -qEe '^[a-z][a-z0-9]*((\.|-)?[a-z][a-z0-9]*)*=.+$'; then
144 | return 0
145 | else
146 | return 1
147 | fi
148 | }
149 |
150 | # usage:
151 | # checkscriptname
152 | # description:
153 | # Check if a script name is valid.
154 | #
155 | checkscriptname()
156 | {
157 | ! checkisdir "$1"
158 | }
159 |
160 | # usage:
161 | # getscript
162 | # description:
163 | # Get a path of a script.
164 | getscript()
165 | {
166 | if [ $# -lt 1 ]; then
167 | err "usage: getscript "
168 | exit ${EX_USAGE}
169 | fi
170 |
171 | local script_name
172 | script_name="$1"
173 |
174 | local dir
175 | for dir in ${RUNSCRIPTS}; do
176 | if [ ! -d "${dir}" ]; then
177 | debug "${dir}: it's not a directory or doesn't exist."
178 | continue
179 | fi
180 |
181 | local pathname
182 | pathname="${dir}/${script_name}"
183 |
184 | if [ ! -f "${pathname}" ]; then
185 | continue
186 | fi
187 |
188 | if [ ! -f "${pathname}" ]; then
189 | warn "${script_name}: it doesn't seem to have the execute bit permission. Ignoring ..."
190 | continue
191 | fi
192 |
193 | printf "%s\n" "${pathname}"
194 | return 0
195 | done
196 |
197 | return 1
198 | }
199 |
200 | # usage:
201 | # checknodename
202 | # description:
203 | # Check if a node name is valid.
204 | #
205 | checknodename()
206 | {
207 | if printf "%s" "$1" | grep -qEe '^[a-zA-Z0-9._@-]+'; then
208 | return 0
209 | else
210 | return 1
211 | fi
212 | }
213 |
214 | # usage:
215 | # checkisdir
216 | # description:
217 | # Check if a value contains a / character.
218 | #
219 | checkisdir()
220 | {
221 | if printf "%s" "$1" | grep -qEe '/'; then
222 | return 0
223 | else
224 | return 1
225 | fi
226 | }
227 |
228 | # usage:
229 | # checkprojectname
230 | # description:
231 | # Check if a project name is valid.
232 | #
233 | checkprojectname()
234 | {
235 | if printf "%s" "$1" | grep -qEe '^[a-zA-Z0-9._-]+$'; then
236 | return 0
237 | else
238 | return 1
239 | fi
240 | }
241 |
242 | # usage:
243 | # checkyesno
244 | # args:
245 | # : In case of an incorrect value, it displays a warning with its name, so that it
246 | # works as a hint.
247 | # : Value to check.
248 | # description:
249 | # Checks whether a value is YES or NO regardless of whether it is uppercase or
250 | # lowercase.
251 | #
252 | checkyesno()
253 | {
254 | case "$2" in
255 | [Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Oo][Nn]|1) return 0 ;;
256 | [Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]|0) return 1 ;;
257 | *) warn "$1 is not set properly."; return 2 ;;
258 | esac
259 | }
260 |
261 | # usage:
262 | # checkproject
263 | # description:
264 | # Check if a project exists.
265 | #
266 | checkproject()
267 | {
268 | if [ $# -lt 1 ]; then
269 | err "usage: checkproject "
270 | exit ${EX_USAGE}
271 | fi
272 |
273 | local project
274 | project="$1"
275 |
276 | local projectdir
277 | projectdir="${PROJECTSDIR}/${project}"
278 |
279 | if [ -d "${projectdir}" ]; then
280 | return 0
281 | else
282 | return 1
283 | fi
284 | }
285 |
286 | # usage:
287 | # checknode
288 | # args:
289 | # : Project where the node can be located.
290 | # : Node name.
291 | # description:
292 | # Check if a node exists.
293 | #
294 | checknode()
295 | {
296 | if [ $# -lt 2 ]; then
297 | err "usage: checknode "
298 | exit ${EX_USAGE}
299 | fi
300 |
301 | local project
302 | project="$1"
303 |
304 | local node
305 | node="$2"
306 |
307 | local nodedir
308 | nodedir="${NODESDIR}/${project}/${node}"
309 |
310 | if [ -d "${nodedir}" ]; then
311 | return 0
312 | else
313 | return 1
314 | fi
315 | }
316 |
317 | # usage:
318 | # checkprojecthealth
319 | # args:
320 | # : Project name to check.
321 | # : Check the project on this node.
322 | # description:
323 | # Checks if a project exists, has a status of 'DONE', has all jails with a status
324 | # of 0, none of its jails are dirty, and has all healthcheckers with a status other
325 | # than 'failing' or 'unhealthy'.
326 | #
327 | # Returns 0 when all checks are successful, 1 when they are not, and 2 when there
328 | # is an error.
329 | #
330 | checkprojecthealth()
331 | {
332 | if [ $# -lt 2 ]; then
333 | err "usage: checkprojecthealth "
334 | exit ${EX_USAGE}
335 | fi
336 |
337 | local project
338 | project="$1"
339 |
340 | local node
341 | node="$2"
342 |
343 | local errlevel
344 |
345 | local errmsg
346 | errmsg=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
347 |
348 | errlevel=$?
349 |
350 | if [ ${errlevel} -eq 0 ]; then
351 | local project_info
352 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe` || return $?
353 |
354 | local state
355 | state=`echo -e "${project_info}" | safe_exc jq -r .state` || return $?
356 |
357 | if [ "${state}" != "DONE" ]; then
358 | warn "Project state is '${state}'"
359 |
360 | return 1
361 | fi
362 |
363 | local service_index=0
364 |
365 | local services
366 | services=`echo -e "${project_info}" | safe_exc jq -r '.services'` || return $?
367 |
368 | local services_length
369 | services_length=`echo -e "${services}" | safe_exc jq -r length` || return $?
370 |
371 | while [ ${service_index} -lt ${services_length} ]; do
372 | local service
373 | service=`echo -e "${services}" | safe_exc jq -r ".[${service_index}]"` || return $?
374 |
375 | local service_status
376 | service_status=`echo -e "${service}" | safe_exc jq -r '.status'` || return $?
377 |
378 | local service_name
379 | service_name=`echo -e "${service}" | safe_exc jq -r '.name'` || return $?
380 |
381 | if [ "${service_status}" != 0 ]; then
382 | warn "Service '${service_name}' has an unexpected status '${service_status}'"
383 |
384 | return 1
385 | fi
386 |
387 | local service_jail
388 | service_jail=`echo -e "${service}" | safe_exc jq -r '.jail'` || return $?
389 |
390 | local is_dirty
391 | is_dirty=`remote_exc "${node}" "NO" "NO" appjail jail get -I -- "${service_jail}" dirty 2>&1`
392 |
393 | if [ $? -ne 0 ]; then
394 | warn "Error checking status of jail '${service_jail}': ${is_dirty}"
395 |
396 | return 2
397 | fi
398 |
399 | if [ ${is_dirty} -eq 1 ]; then
400 | warn "Jail '${service_jail}' is dirty"
401 |
402 | return 1
403 | fi
404 |
405 | local healthcheckers_status
406 | healthcheckers_status=`remote_exc "${node}" "NO" "NO" appjail healthcheck list -epHIt -- "${service_jail}" status 2>&1`
407 |
408 | if [ $? -ne 0 ]; then
409 | warn "Error checking status of healthcheckers of jail '${service_jail}': ${healthcheckers_status}"
410 |
411 | return 2
412 | fi
413 |
414 | local healthchecker_status
415 | for healthchecker_status in ${healthcheckers_status}; do
416 | case "${healthchecker_status}" in
417 | failing|unhealthy)
418 | warn "Service '${service_name}' has a healthchecker with an unexpected status '${healthchecker_status}'"
419 |
420 | return 1
421 | ;;
422 | esac
423 | done
424 |
425 | service_index=$((service_index+1))
426 | done
427 |
428 | return 0
429 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
430 | warn "Project '${project}' not found."
431 |
432 | return 1
433 | else
434 | warn "Error checking status of project '${project}': ${errmsg}"
435 |
436 | return 2
437 | fi
438 | }
439 |
440 | # usage:
441 | # checkport
442 | # description:
443 | # Check if in a range between 1 and 65535.
444 | #
445 | checkport()
446 | {
447 | if [ $# -lt 1 ]; then
448 | err "usage: checkport "
449 | exit ${EX_USAGE}
450 | fi
451 |
452 | local port
453 | port="$1"
454 |
455 | if [ ${port} -lt 1 -o ${port} -gt 65535 ]; then
456 | return 1
457 | else
458 | return 0
459 | fi
460 | }
461 |
462 | # usage:
463 | # safe_exc [ ...]
464 | # description:
465 | # Executes a command and exits if it returns a status other than 0. It needs to
466 | # place the output in a buffer before displaying it and if an error occurs, the
467 | # err() function is used to display the output.
468 | #
469 | safe_exc()
470 | {
471 | local errlevel
472 | local output
473 |
474 | debug "Executing '$*'"
475 |
476 | output=`"$@" 2>&1`
477 |
478 | errlevel=$?
479 |
480 | if [ ${errlevel} -ne 0 ]; then
481 | err "${output}"
482 | exit ${errlevel}
483 | fi
484 |
485 | if [ -n "${output}" ]; then
486 | printf "%s\n" "${output}"
487 | fi
488 |
489 | return ${errlevel}
490 | }
491 |
492 | # usage:
493 | # debug ...
494 | # description:
495 | # If DEBUG is set to YES, displays a message using the debug level.
496 | #
497 | debug()
498 | {
499 | if checkyesno "DEBUG" "${DEBUG}"; then
500 | stderr "[${COLOR_GRAY} debug${COLOR_DEFAULT} ] :: <${COLOR_GRAY} ${NAME} ${COLOR_DEFAULT}> $*"
501 | fi
502 | }
503 |
504 | # usage:
505 | # info ...
506 | # description:
507 | # Display a message using the info level.
508 | #
509 | info()
510 | {
511 | stderr "[${COLOR_LIGHT_BLUE} info ${COLOR_DEFAULT} ] :: <${COLOR_LIGHT_BLUE} ${NAME} ${COLOR_DEFAULT}> $*"
512 | }
513 |
514 | # usage:
515 | # err ...
516 | # description:
517 | # Display a message using the err level.
518 | #
519 | err()
520 | {
521 | stderr "[${COLOR_RED} error${COLOR_DEFAULT} ] :: <${COLOR_RED} ${NAME} ${COLOR_DEFAULT}> $*"
522 | }
523 |
524 | # usage:
525 | # warn ...
526 | # description:
527 | # Display a message using the warn level.
528 | #
529 | warn()
530 | {
531 | stderr "[${COLOR_LIGHT_YELLOW} warn ${COLOR_DEFAULT} ] :: <${COLOR_LIGHT_YELLOW} ${NAME} ${COLOR_DEFAULT}> $*"
532 | }
533 |
534 | # usage:
535 | # stdout ...
536 | # description:
537 | # Display a message via stdout.
538 | #
539 | stdout()
540 | {
541 | print "$*"
542 | }
543 |
544 | # usage:
545 | # stderr ...
546 | # description:
547 | # Display a message via stderr.
548 | #
549 | stderr()
550 | {
551 | print "$*" >&2
552 | }
553 |
554 | # usage:
555 | # print ...
556 | # description:
557 | # Display a message via print.
558 | #
559 | print()
560 | {
561 | echo -e "$*"
562 | }
563 |
564 | # usage:
565 | # remote_exc ...
566 | # args:
567 | # : Node to connect to and execute the command.
568 | # : If YES, use the safe_exc() function to execute the command.
569 | # : If NO, use sansi(1) to remove those control characters.
570 | # : Command and its arguments.
571 | # description:
572 | # Remotely execute a command on a node.
573 | #
574 | remote_exc()
575 | {
576 | if [ $# -lt 3 ]; then
577 | err "usage: remote_exc [YES|NO] [YES|NO] [ ...]"
578 | exit ${EX_USAGE}
579 | fi
580 |
581 | checkdependency sansi
582 |
583 | local node
584 | node="$1"
585 |
586 | local use_safe_exc
587 | use_safe_exc="$2"
588 |
589 | local safe_exc_cmd
590 | safe_exc_cmd=
591 |
592 | if checkyesno "use_safe_exc" "${use_safe_exc}"; then
593 | safe_exc_cmd="safe_exc"
594 | fi
595 |
596 | local dirty
597 | dirty="$3"
598 |
599 | shift 3
600 |
601 | local be_dirty=false
602 |
603 | if checkyesno "dirty" "${dirty}"; then
604 | be_dirty=true
605 | fi
606 |
607 | if [ ! -d "${SOCKETSDIR}" ]; then
608 | safe_exc mkdir -p -- "${SOCKETSDIR}"
609 | fi
610 |
611 | if ${be_dirty}; then
612 | ${safe_exc_cmd} ssh -t \
613 | -o LogLevel="${SSH_LOGLEVEL}" \
614 | -o BatchMode=yes \
615 | -o ControlMaster=auto \
616 | -o ControlPath="${SOCKETSDIR}/${CONTROLPATH}" \
617 | -o ControlPersist="${CONTROLPERSIST}" \
618 | -- "${node}" "$@"
619 |
620 | return $?
621 | else
622 | local errlevel
623 |
624 | local buff
625 | buff=`${safe_exc_cmd} ssh -t -o LogLevel="${SSH_LOGLEVEL}" -o BatchMode=yes -o ControlMaster=auto -o ControlPath="${SOCKETSDIR}/${CONTROLPATH}" -o ControlPersist="${CONTROLPERSIST}" -- "${node}" "$@"`
626 |
627 | errlevel=$?
628 |
629 | if checkyesno "use_safe_exc" "${use_safe_exc}" && [ ${errlevel} -ne 0 ]; then
630 | exit ${errlevel}
631 | fi
632 |
633 | if [ -n "${buff}" ]; then
634 | printf "%s\n" "${buff}" | sansi
635 | fi
636 |
637 | return ${errlevel}
638 | fi
639 | }
640 |
641 | # usage:
642 | # deploy
643 | # args:
644 | # : Project to deploy.
645 | # : Node to the deploy the project.
646 | # description:
647 | # Deploy a project to a specific node. This function performs basic checks with
648 | # checkprojecthealth() to decide if the project needs to be destroyed before
649 | # redeploying. If the project has a status other than 'DONE', has a jail with a
650 | # status other than 0, or even fails the basic project health check, it will be
651 | # destroyed before deployment. Of course, the project will be deployed if it does
652 | # not exist.
653 | #
654 | deploy()
655 | {
656 | if [ $# -lt 2 ]; then
657 | err "usage: deploy "
658 | exit ${EX_USAGE}
659 | fi
660 |
661 | checkdependency jq
662 |
663 | local project
664 | project="$1"
665 |
666 | local node
667 | node="$2"
668 |
669 | info "[project:${project} / node:${node}]:"
670 |
671 | local errlevel
672 |
673 | local output
674 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
675 |
676 | errlevel=$?
677 |
678 | if [ ${errlevel} -eq 0 ]; then
679 | local project_info
680 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe` || exit $?
681 |
682 | local state
683 | state=`echo -e "${project_info}" | safe_exc jq -r .state` || exit $?
684 |
685 | debug "Project state is '${state}'"
686 |
687 | local destroy=false
688 |
689 | if [ "${state}" = "DONE" ]; then
690 | local services_status
691 | services_status=`echo -e "${project_info}" | safe_exc jq -r '.services.[].status'` || exit $?
692 |
693 | local service_index=0
694 |
695 | local service_status
696 | for service_status in ${services_status}; do
697 | if [ "${service_status}" != 0 ]; then
698 | destroy=true
699 |
700 | local service_name
701 | service_name=`echo -e "${project_info}" | safe_exc jq -r ".service.[${service_index}].name"` || exit $?
702 |
703 | warn "Service '${service_name}' has a different status (${service_status}) than expected"
704 |
705 | break
706 | fi
707 |
708 | service_index=$((service_index+1))
709 | done
710 | else
711 | destroy=true
712 |
713 | warn "State (${state}) is different than expected"
714 | fi
715 |
716 | if ${destroy}; then
717 | destroy "${project}" "${node}"
718 | fi
719 | elif [ ${errlevel} -ne ${EX_NOINPUT} ]; then
720 | if [ -n "${output}" ]; then
721 | warn "Project '${project}' has an error: ${output}"
722 | else
723 | warn "Project '${project}' has an error"
724 | fi
725 |
726 | destroy "${project}" "${node}"
727 | fi
728 |
729 | local projectdir="${PROJECTSDIR}/${project}"
730 |
731 | remote_exc "${node}" "YES" "NO" mkdir -p -- "\"${REMOTE_PROJECTSDIR}/${project}\""
732 |
733 | (cd "${projectdir}"; mirror . "${node}:${REMOTE_PROJECTSDIR}/${project}") || exit $?
734 |
735 | remote_exc "${node}" "YES" "NO" \
736 | "cd \"${REMOTE_PROJECTSDIR}/${project}\"" \; \
737 | env "DIRECTOR_PROJECT=${project}" \
738 | "LITTLEJET_NODE=${node}" \
739 | appjail-director up
740 | }
741 |
742 | # usage:
743 | # sequencial_deploy
744 | # args:
745 | # : Project to deploy.
746 | # : Space-separated list of nodes.
747 | # description:
748 | # Like parallel_deploy() but deploying all nodes in sequencial fashion.
749 | #
750 | sequencial_deploy()
751 | {
752 | if [ $# -lt 2 ]; then
753 | err "usage: sequencial_deploy "
754 | exit ${EX_USAGE}
755 | fi
756 |
757 | local project
758 | project="$1"
759 |
760 | local nodes
761 | nodes="$2"
762 |
763 | for node in ${nodes}; do
764 | deploy "${project}" "${node}"
765 | done
766 | }
767 |
768 | # usage:
769 | # parallel_deploy
770 | # args:
771 | # : Project to deploy.
772 | # : Space-separated list of nodes.
773 | # description:
774 | # Like deploy() but deploying all nodes in parallel up to the limit set by the NCPU
775 | # parameter.
776 | #
777 | parallel_deploy()
778 | {
779 | if [ $# -lt 2 ]; then
780 | err "usage: parallel_deploy "
781 | exit ${EX_USAGE}
782 | fi
783 |
784 | local project
785 | project="$1"
786 |
787 | local nodes
788 | nodes="$2"
789 |
790 | local output
791 | output=`tempdir` || exit $?
792 |
793 | atexit_add "removedir \"${output}\""
794 |
795 | local nproc=0
796 |
797 | local node
798 | for node in ${nodes}; do
799 | deploy "${project}" "${node}" > "${output}/${node}.out" 2>&1 &
800 |
801 | atexit_add "safe_kill $! $$"
802 |
803 | nproc=$((nproc+1))
804 |
805 | local errlevel
806 |
807 | debug "Job:${nproc}, Total:${NCPU}"
808 |
809 | test "${nproc}" -ge "${NCPU}"
810 |
811 | errlevel=$?
812 |
813 | if [ ${errlevel} -eq 0 ]; then
814 | debug "Waiting for '${nproc}' jobs"
815 |
816 | nproc=0
817 |
818 | wait || exit $?
819 | elif [ ${errlevel} -eq 1 ]; then
820 | continue
821 | else
822 | err "Incorrect value for parameter 'NCPU'"
823 | exit ${EX_CONFIG}
824 | fi
825 | done
826 |
827 | wait || exit $?
828 |
829 | for node in ${nodes}; do
830 | local outfile
831 | outfile="${output}/${node}.out"
832 |
833 | if [ -f "${outfile}" ]; then
834 | cat -- "${outfile}"
835 | fi
836 | done
837 |
838 | removedir "${output}"
839 | }
840 |
841 | # usage:
842 | # destroy
843 | # args:
844 | # : Project to destroy.
845 | # : Destroy the project on this node.
846 | # description:
847 | # Attempts to remotely destroy a project and then destroy it locally. It may or may
848 | # not be remotely destroyed due to some unspecified error (e.g. connection error, etc.)
849 | # and jails may or may not be destroyed if they fail to stop or destroy.
850 | #
851 | destroy()
852 | {
853 | if [ $# -lt 2 ]; then
854 | err "usage: destroy "
855 | exit ${EX_USAGE}
856 | fi
857 |
858 | local project
859 | project="$1"
860 |
861 | local node
862 | node="$2"
863 |
864 | warn "Destroying project '${project}' on '${node}'"
865 |
866 | run_director "${project}" "${node}" "NO" "NO" \
867 | down --ignore-failed -d
868 |
869 | local remote_projectdir
870 | remote_projectdir="${REMOTE_PROJECTSDIR}/${project}"
871 |
872 | debug "Removing '${node}:${remote_projectdir}'"
873 |
874 | remote_exc "${node}" "NO" "NO" rm -rf -- "${remote_projectdir}"
875 | }
876 |
877 | # usage:
878 | # load_config
879 | # description:
880 | # Load the file specified in the LITTLEJET_DEFAULT_CONFIG and LITTLEJET_USER_CONFIG
881 | # environment variables as your configuration files.
882 | #
883 | load_config()
884 | {
885 | local default_config
886 | default_config="${LITTLEJET_DEFAULT_CONFIG}"
887 |
888 | if [ -z "${default_config}" ]; then
889 | err "LITTLEJET_DEFAULT_CONFIG: environment variable hasn't been defined."
890 | exit ${EX_CONFIG}
891 | fi
892 |
893 | if [ ! -f "${default_config}" ]; then
894 | err "${default_config}: default configuration file cannot be found."
895 | exit ${EX_NOINPUT}
896 | fi
897 |
898 | . "${default_config}"
899 |
900 | local user_config
901 | user_config="${LITTLEJET_USER_CONFIG}"
902 |
903 | if [ -n "${user_config}" ]; then
904 | if [ ! -f "${user_config}" ]; then
905 | err "${user_config}: user configuration file cannot be found."
906 | exit ${EX_NOINPUT}
907 | fi
908 |
909 | . "${user_config}"
910 | fi
911 | }
912 |
913 | # usage:
914 | # mirror ...
915 | # description:
916 | # Wrapper for cpdup(1) with -s0 and -i0 set. This also sets -X to point to the file
917 | # specified by the CPIGNORE parameter.
918 | #
919 | mirror()
920 | {
921 | if [ $# -eq 0 ]; then
922 | err "usage: mirror ..."
923 | exit ${EX_USAGE}
924 | fi
925 |
926 | checkdependency cpdup
927 |
928 | local output
929 |
930 | output=`safe_exc cpdup -s0 -i0 -X "${CPIGNORE}" "$@"` || exit $?
931 | output=`echo -e "${output}" | sed -Ee '/Handshaked with .+/d'`
932 |
933 | if [ -n "${output}" ]; then
934 | info "cpdup: ${output}"
935 | fi
936 | }
937 |
938 | # usage:
939 | # tempdir
940 | # description:
941 | # Create a temporary directory.
942 | #
943 | tempdir()
944 | {
945 | safe_exc mktemp -dt littlejet
946 | }
947 |
948 | # usage:
949 | # tempfile
950 | # description:
951 | # Create a temporary file.
952 | tempfile()
953 | {
954 | safe_exc mktemp -t littlejet
955 | }
956 |
957 | # usage:
958 | # testnode
959 | # description:
960 | # Performs a basic check: attempts to connect to the node and runs the true(1) utility.
961 | # If the connection fails for any reason, the test fails.
962 | #
963 | testnode()
964 | {
965 | local node
966 | node="$1"
967 |
968 | if [ -z "${node}" ]; then
969 | err "usage: testnode "
970 | exit ${EX_USAGE}
971 | fi
972 |
973 | remote_exc "${node}" "NO" "NO" true
974 | }
975 |
976 | # usage:
977 | # run_director ...
978 | # args:
979 | # : Node to connect to and execute the command.
980 | # : If YES, use the safe_exc() function to execute the command.
981 | # : If NO, use sansi(1) to remove those control characters.
982 | # : Command and its arguments.
983 | # description:
984 | # Remotely execute a command on a node.
985 | #
986 | run_director()
987 | {
988 | if [ $# -eq 0 ]; then
989 | err "usage: run_director [YES|NO] [YES|NO] [ ...]"
990 | exit ${EX_USAGE}
991 | fi
992 |
993 | local project
994 | project="$1"
995 |
996 | local node
997 | node="$2"
998 |
999 | local safe_exc
1000 | safe_exc="$3"
1001 |
1002 | local be_dirty
1003 | be_dirty="$4"
1004 |
1005 | shift 4
1006 |
1007 | local use_workdir
1008 |
1009 | local errlevel
1010 |
1011 | local remote_projectdir
1012 | remote_projectdir="${REMOTE_PROJECTSDIR}/${project}"
1013 |
1014 | local output
1015 | output=`remote_exc "${node}" "NO" "NO" test -d "\"${remote_projectdir}\"" 2>&1`
1016 |
1017 | errlevel=$?
1018 |
1019 | if [ ${errlevel} -eq 0 ]; then
1020 | use_workdir=true
1021 | elif [ ${errlevel} -eq 1 ]; then
1022 | use_workdir=false
1023 | else
1024 | use_workdir=false
1025 |
1026 | warn "Could not use working directory '${remote_projectdir}': ${output}"
1027 | fi
1028 |
1029 | if ${use_workdir}; then
1030 | remote_exc "${node}" "${safe_exc}" "${be_dirty}" \
1031 | cd "\"${remote_projectdir}\";" \
1032 | env "DIRECTOR_PROJECT=${project}" \
1033 | "LITTLEJET_NODE=${node}" \
1034 | appjail-director "$@"
1035 | else
1036 | remote_exc "${node}" "${safe_exc}" "${be_dirty}" \
1037 | env "DIRECTOR_PROJECT=${project}" \
1038 | "LITTLEJET_NODE=${node}" \
1039 | appjail-director "$@"
1040 | fi
1041 | }
1042 |
1043 | # usage:
1044 | # humanize_number
1045 | # description:
1046 | # Humanize a number as specified in humanize_number(3).
1047 | #
1048 | humanize_number()
1049 | {
1050 | if [ $# -lt 1 ]; then
1051 | err "usage: humanize_number "
1052 | exit ${EX_USAGE}
1053 | fi
1054 |
1055 | local number
1056 | number="$1"
1057 |
1058 | if ! is_human_number "${number}"; then
1059 | err "usage: humanize_number "
1060 | exit ${EX_DATAERR}
1061 | fi
1062 |
1063 | local multiplier
1064 |
1065 | case "${number}" in
1066 | *k)
1067 | multiplier=1000
1068 | ;;
1069 | *K)
1070 | multiplier=1024
1071 | ;;
1072 | *m)
1073 | multiplier=1000000
1074 | ;;
1075 | *M)
1076 | multiplier=1048576
1077 | ;;
1078 | *g)
1079 | multiplier=1000000000
1080 | ;;
1081 | *G)
1082 | multiplier=1073741824
1083 | ;;
1084 | *t)
1085 | multiplier=1000000000000
1086 | ;;
1087 | *T)
1088 | multiplier=1099511627776
1089 | ;;
1090 | *p)
1091 | multiplier=1000000000000000
1092 | ;;
1093 | *P)
1094 | multiplier=1125899906842624
1095 | ;;
1096 | *e)
1097 | multiplier=1000000000000000000
1098 | ;;
1099 | *E)
1100 | multiplier=1152921504606846976
1101 | ;;
1102 | *)
1103 | if checknumber "${number}"; then
1104 | echo "${number}"
1105 | return ${EX_OK}
1106 | else
1107 | err "${number}: invalid number."
1108 | exit ${EX_DATAERR}
1109 | fi
1110 | ;;
1111 | esac
1112 |
1113 | local bak_number
1114 | bak_number="${number}"
1115 |
1116 | number=`printf "%s" "${number}" | sed -Ee 's/^([0-9]+)[kKmMgGtTpPeE]$/\1/'`
1117 |
1118 | if [ ${number} -eq 0 ]; then
1119 | echo 0
1120 | return ${EX_OK}
1121 | fi
1122 |
1123 | number=$((number*multiplier))
1124 |
1125 | if [ ${number} -le 0 ]; then
1126 | err "${bak_number}: number too long."
1127 | exit ${EX_DATAERR}
1128 | fi
1129 |
1130 | echo "${number}"
1131 |
1132 | return ${EX_OK}
1133 | }
1134 |
1135 | # usage:
1136 | # is_human_number
1137 | # description:
1138 | # Check if a number is valid for humanization.
1139 | #
1140 | is_human_number()
1141 | {
1142 | if [ $# -lt 1 ]; then
1143 | err "usage: is_human_number "
1144 | exit ${EX_USAGE}
1145 | fi
1146 |
1147 | local number
1148 | number="$1"
1149 |
1150 | if printf "%s" "${number}" | grep -qEe '^[0-9]+[kKmMgGtTpPeE]?$'; then
1151 | return 0
1152 | else
1153 | return 1
1154 | fi
1155 | }
1156 |
1157 | # usage:
1158 | # checknumber
1159 | # description:
1160 | # Check if the string is a valid number.
1161 | checknumber()
1162 | {
1163 | if [ $# -lt 1 ]; then
1164 | err "usage: checknumber "
1165 | exit ${EX_USAGE}
1166 | fi
1167 |
1168 | local number
1169 | number="$1"
1170 |
1171 | local errlevel
1172 |
1173 | test 0 -eq "${number}" 2> /dev/null
1174 |
1175 | errlevel=$?
1176 |
1177 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
1178 | return 0
1179 | else
1180 | return 1
1181 | fi
1182 | }
1183 |
1184 | # usage:
1185 | # safe_kill [] []
1186 | # args:
1187 | # : Pid to kill.
1188 | # : Expected parent process ID. Default: the process ID of the script itself.
1189 | # : Wait the specified time before exiting with a status other than 0. Default: 30
1190 | #
1191 | safe_kill()
1192 | {
1193 | if [ $# -lt 1 ]; then
1194 | err "usage: safe_kill [] []"
1195 | exit ${EX_USAGE}
1196 | fi
1197 |
1198 | local pid
1199 | pid=$1
1200 |
1201 | local expected_pid
1202 | expected_pid="$2"
1203 |
1204 | if [ -z "${expected_pid}" ]; then
1205 | expected_pid=$$
1206 | fi
1207 |
1208 | local duration
1209 | duration="${3:-30}"
1210 |
1211 | local ppid
1212 | ppid=`get_ppid ${pid}`
1213 |
1214 | local errlevel=0
1215 |
1216 | # Process does not exist.
1217 | if [ -z "${ppid}" ]; then
1218 | return ${errlevel}
1219 | fi
1220 |
1221 | if [ ${ppid} -eq ${expected_pid} ]; then
1222 | kill ${pid} > /dev/null 2>&1
1223 |
1224 | pwait -o -t "${duration}" ${pid} > /dev/null 2>&1
1225 |
1226 | errlevel=$?
1227 |
1228 | if [ ${errlevel} -eq 124 ]; then
1229 | warn "Timeout has been reached, pid ${pid} is still running!"
1230 | fi
1231 | fi
1232 |
1233 | return ${errlevel}
1234 | }
1235 |
1236 | # usage:
1237 | # random_number
1238 | # description:
1239 | # Generates a random number between a specific range.
1240 | #
1241 | random_number()
1242 | {
1243 | if [ $# -lt 2 ]; then
1244 | err "usage: random_number "
1245 | exit ${EX_USAGE}
1246 | fi
1247 |
1248 | local begin
1249 | begin="$1"
1250 |
1251 | local end
1252 | end="$2"
1253 |
1254 | jot -r 1 "${begin}" "${end}"
1255 | }
1256 |
1257 | # usage:
1258 | # kill_proc_tree
1259 | # description:
1260 | # Use safest_kill() to kill the pid and its child processes.
1261 | #
1262 | kill_proc_tree()
1263 | {
1264 | if [ $# -lt 1 ]; then
1265 | err "usage: kill_proc_tree "
1266 | exit ${EX_USAGE}
1267 | fi
1268 |
1269 | local pid
1270 | pid="$1"
1271 |
1272 | local tokill
1273 | for tokill in `get_proc_tree "${pid}" | tail -r`; do
1274 | safest_kill "${tokill}"
1275 | done
1276 | }
1277 |
1278 | # usage:
1279 | # safest_kill
1280 | # description:
1281 | # This is considered a bit safer than safe_kill() because it attempts to send multiple
1282 | # SIGTERM signals at random seconds between alternate sleeps and if the process
1283 | # refuses to terminate, it will be terminated using the SIGKILL signal.
1284 | #
1285 | safest_kill()
1286 | {
1287 | if [ $# -lt 1 ]; then
1288 | err "usage: safest_kill "
1289 | exit ${EX_USAGE}
1290 | fi
1291 |
1292 | local pid
1293 | pid=$1
1294 |
1295 | local retry=1
1296 | local total=3
1297 |
1298 | while [ ${retry} -le ${total} ]; do
1299 | debug "Sending SIGTERM (${retry}/${total}) -> ${pid}"
1300 |
1301 | kill ${pid} > /dev/null 2>&1
1302 |
1303 | if ! check_proc ${pid}; then
1304 | return 0
1305 | fi
1306 |
1307 | sleep `random_number 1 3`.`random_number 3 9`
1308 |
1309 | retry=$((retry+1))
1310 | done
1311 |
1312 | if check_proc ${pid}; then
1313 | debug "Sending SIGKILL -> ${pid}"
1314 |
1315 | kill -KILL ${pid} > /dev/null 2>&1
1316 | fi
1317 | }
1318 |
1319 | # usage:
1320 | # check_proc
1321 | # description:
1322 | # Check if the process exists.
1323 | check_proc()
1324 | {
1325 | if [ $# -lt 1 ]; then
1326 | err "usage: check_proc "
1327 | exit ${EX_USAGE}
1328 | fi
1329 |
1330 | local pid
1331 | pid=$1
1332 |
1333 | if [ `ps -o pid -p ${pid} | wc -l` -eq 2 ]; then
1334 | return 0
1335 | else
1336 | return 1
1337 | fi
1338 | }
1339 |
1340 | # usage:
1341 | # get_proc_tree
1342 | # description:
1343 | # Get a list of child processes from the specified parent PID.
1344 | #
1345 | get_proc_tree()
1346 | {
1347 | if [ $# -lt 1 ]; then
1348 | err "usage: get_proc_tree "
1349 | exit ${EX_USAGE}
1350 | fi
1351 |
1352 | local ppid
1353 | ppid=$1
1354 |
1355 | local pid
1356 | for pid in `pgrep -P ${ppid}`; do
1357 | echo ${pid}
1358 |
1359 | get_proc_tree ${pid}
1360 | done
1361 | }
1362 |
1363 | # usage:
1364 | # get_ppid
1365 | # description:
1366 | # Get the parent ID of the specified process ID.
1367 | #
1368 | get_ppid()
1369 | {
1370 | if [ $# -lt 1 ]; then
1371 | err "usage: get_ppid "
1372 | exit ${EX_USAGE}
1373 | fi
1374 |
1375 | local pid
1376 | pid="$1"
1377 |
1378 | ps -p "${pid}" -o ppid | grep -v PPID | awk '{print $1}'
1379 | }
1380 |
1381 | # usage:
1382 | # atexit_init [] []
1383 | # args:
1384 | # : Signals to ignore.
1385 | # : Signals to handle.
1386 | # description:
1387 | # Configures the signals to be handled and ignored. The atexit_cleanup() function
1388 | # is executed on exit or when any of the handled signals are triggered.
1389 | #
1390 | atexit_init()
1391 | {
1392 | _ATEXIT_IGNORED_SIGNALS="$1"
1393 | if [ -z "${_ATEXIT_IGNORED_SIGNALS}" ]; then
1394 | _ATEXIT_IGNORED_SIGNALS="SIGALRM SIGVTALRM SIGPROF SIGUSR1 SIGUSR2"
1395 | fi
1396 |
1397 | _ATEXIT_HANDLED_SIGNALS="$2"
1398 | if [ -z "${_ATEXIT_HANDLED_SIGNALS}" ]; then
1399 | _ATEXIT_HANDLED_SIGNALS="SIGHUP SIGINT SIGQUIT SIGTERM SIGXCPU SIGXFSZ"
1400 | fi
1401 |
1402 | debug "Creating atexit() file"
1403 |
1404 | local errlevel
1405 |
1406 | _ATEXIT_FILE=`tempfile`
1407 |
1408 | errlevel=$?
1409 |
1410 | if [ ${errlevel} -ne 0 ]; then
1411 | err "atexit() error: ${_ATEXIT_FILE}"
1412 | exit ${errlevel}
1413 | fi
1414 |
1415 | debug "atexit file() is '${_ATEXIT_FILE}'"
1416 | debug "Signals to ignore: ${_ATEXIT_IGNORED_SIGNALS}"
1417 | debug "Signals to handle: ${_ATEXIT_HANDLED_SIGNALS}"
1418 |
1419 | trap '' ${_ATEXIT_IGNORED_SIGNALS}
1420 | trap "_ATEXIT_ERRLEVEL=\$?; atexit_cleanup; exit \${_ATEXIT_ERRLEVEL}" EXIT
1421 | trap "atexit_cleanup; exit 70" ${_ATEXIT_HANDLED_SIGNALS}
1422 | }
1423 |
1424 | # usage:
1425 | # atexit_cleanup
1426 | # description:
1427 | # This function will be executed when a signal configured in atexit_init() is
1428 | # triggered.
1429 | #
1430 | atexit_cleanup()
1431 | {
1432 | # ignore
1433 | trap '' ${_ATEXIT_HANDLED_SIGNALS} EXIT
1434 |
1435 | if [ -f "${_ATEXIT_FILE}" ]; then
1436 | /bin/sh "${_ATEXIT_FILE}"
1437 | rm -f -- "${_ATEXIT_FILE}"
1438 | fi
1439 |
1440 | # restore
1441 | trap - ${_ATEXIT_IGNORED_SIGNALS} ${_ATEXIT_HANDLED_SIGNALS} EXIT
1442 | }
1443 |
1444 | # usage:
1445 | # atexit_add [ ...]
1446 | # description:
1447 | # Adds a command to the atexit list that will be executed when a signal configured
1448 | # in atexit_init() is triggered.
1449 | #
1450 | atexit_add()
1451 | {
1452 | if [ $# -eq 0 ]; then
1453 | err "usage: atexit_add [ ...]"
1454 | exit ${EX_USAGE}
1455 | fi
1456 |
1457 | local command
1458 | command="$@"
1459 |
1460 | if [ ! -f "${_ATEXIT_FILE}" ]; then
1461 | err "You must first execute atexit_init() function before use this function."
1462 | exit ${EX_NOINPUT}
1463 | fi
1464 |
1465 | if ! printf "%s\n" "${command}" >> "${_ATEXIT_FILE}"; then
1466 | err "Could not add a command to the atexit file."
1467 | exit ${EX_IOERR}
1468 | fi
1469 | }
1470 |
1471 | # usage:
1472 | # removedir
1473 | # args:
1474 | # : Directory to remove.
1475 | # description:
1476 | # Remove a directory if exists.
1477 | #
1478 | removedir()
1479 | {
1480 | if [ $# -lt 1 ]; then
1481 | err "usage: removedir "
1482 | exit ${EX_USAGE}
1483 | fi
1484 |
1485 | local dir
1486 | dir="$1"
1487 |
1488 | if [ -d "${dir}" ]; then
1489 | rm -rf -- "${dir}"
1490 | fi
1491 | }
1492 |
1493 | # usage:
1494 | # removefile
1495 | # args:
1496 | # : File to remove.
1497 | # description:
1498 | # Remove a file if exists.
1499 | #
1500 | removefile()
1501 | {
1502 | if [ $# -lt 1 ]; then
1503 | err "usage: removefile "
1504 | exit ${EX_USAGE}
1505 | fi
1506 |
1507 | local file
1508 | file="$1"
1509 |
1510 | if [ -f "${file}" ]; then
1511 | rm -f -- "${file}"
1512 | fi
1513 | }
1514 |
--------------------------------------------------------------------------------
/share/littlejet/files/user.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Welcome to your LittleJet configuration file!
3 | #
4 | # Please note that not all parameters are included here, only those that suit most
5 | # environments, but may not yours.
6 | #
7 | # See littlejet.conf(5) for a more detailed description of each parameter.
8 | #
9 |
10 | #
11 | # Number of CPUs to use when performing a task in parallel.
12 | #
13 | #NCPU=
14 |
15 | #
16 | # Enable the debug level.
17 | #
18 | #DEBUG="YES"
19 |
20 | #
21 | # Default space-separated list of directories to search for RunScripts.
22 | #
23 | #RUNSCRIPTS="${LITTLEJETDIR}/runscripts ${SHAREDIR}/runscripts"
24 |
25 | #
26 | # Value for the ControlPersist parameter of ssh(1).
27 | #
28 | #CONTROLPERSIST="8m"
29 |
30 | #
31 | # Displays the healthcheckers when the jail has one or more defined.
32 | #
33 | #SHOW_HEALTHCHECKERS=1
34 |
35 | #
36 | # Displays the limits when the jail has one or more defined.
37 | #
38 | #SHOW_LIMITS=1
39 |
40 | #
41 | # Displays the stats of each jail when the system has rctl(8) enabled.
42 | #
43 | #SHOW_STATS=1
44 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.all:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.all"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | atexit_init
63 | atexit_add ". \"${lib_subr}\""
64 | atexit_add "setname \"${DEPLOY_NAME}\""
65 | atexit_add "load_config"
66 |
67 | local nodes
68 | nodes=`jet get-nodes "${project}"` || exit $?
69 |
70 | if [ -z "${nodes}" ]; then
71 | exit ${EX_CANTCREAT}
72 | fi
73 |
74 | info "Deploying '${project}' in all nodes (parallel) ..."
75 |
76 | parallel_deploy "${project}" "${nodes}"
77 |
78 | exit ${EX_OK}
79 | }
80 |
81 | main "$@"
82 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.all.seq:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.all"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | atexit_init
63 | atexit_add ". \"${lib_subr}\""
64 | atexit_add "setname \"${DEPLOY_NAME}\""
65 | atexit_add "load_config"
66 |
67 | local nodes
68 | nodes=`jet get-nodes "${project}"` || exit $?
69 |
70 | if [ -z "${nodes}" ]; then
71 | exit ${EX_CANTCREAT}
72 | fi
73 |
74 | info "Deploying '${project}' in all nodes (sequencial) ..."
75 |
76 | sequencial_deploy "${project}" "${nodes}"
77 |
78 | exit ${EX_OK}
79 | }
80 |
81 | main "$@"
82 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.each:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.each"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | local nodes
63 | nodes=`jet get-nodes "${project}"` || exit $?
64 |
65 | if [ -z "${nodes}" ]; then
66 | exit ${EX_CANTCREAT}
67 | fi
68 |
69 | local node
70 | for node in ${nodes}; do
71 | local errlevel
72 |
73 | local output
74 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
75 |
76 | errlevel=$?
77 |
78 | if [ ${errlevel} -eq ${EX_OK} ]; then
79 | # ignore
80 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
81 | deploy "${project}" "${node}"
82 | break
83 | else
84 | warn "Error checking for existence of project '${project}' on node '${node}': ${output}"
85 | fi
86 | done
87 |
88 | exit ${EX_OK}
89 | }
90 |
91 | main "$@"
92 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.once:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.once"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | local nodes
63 | nodes=`jet get-nodes "${project}"` || exit $?
64 |
65 | if [ -z "${nodes}" ]; then
66 | exit ${EX_CANTCREAT}
67 | fi
68 |
69 | local deploy=true
70 |
71 | local node
72 | for node in ${nodes}; do
73 | if run_director "${project}" "${node}" "NO" "NO" check; then
74 | deploy=false
75 |
76 | info "${project}: project already deployed."
77 | break
78 | fi
79 | done
80 |
81 | if ${deploy}; then
82 | node=`printf "%s\n" "${nodes}" | sort -R | head -1`
83 |
84 | deploy "${project}" "${node}"
85 | fi
86 |
87 | exit ${EX_OK}
88 | }
89 |
90 | main "$@"
91 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.random:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.random"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | local nodes
63 | nodes=`jet get-nodes "${project}"` || exit $?
64 |
65 | if [ -z "${nodes}" ]; then
66 | exit ${EX_CANTCREAT}
67 | fi
68 |
69 | local node
70 | node=`printf "%s\n" "${nodes}" | sort -R | head -1`
71 |
72 | deploy "${project}" "${node}"
73 |
74 | exit ${EX_OK}
75 | }
76 |
77 | main "$@"
78 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.scale:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.scale"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | return 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | return 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | checkdependency jq
55 |
56 | local project
57 | project="${LITTLEJET_PROJECT}"
58 |
59 | if [ -z "${project}" ]; then
60 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
61 | return ${EX_CONFIG}
62 | fi
63 |
64 | if ! checkprojectname "${project}"; then
65 | err "${project}: invalid project name."
66 | exit ${EX_DATAERR}
67 | fi
68 |
69 | if ! checkproject "${project}"; then
70 | err "${project}: project cannot be found."
71 | exit ${EX_NOINPUT}
72 | fi
73 |
74 | atexit_init
75 | atexit_add ". \"${lib_subr}\""
76 | atexit_add "setname \"${DEPLOY_NAME}\""
77 | atexit_add "load_config"
78 |
79 | local _o
80 | local min=1
81 | local max=0
82 | local rctl_rules=
83 | local stabilization_window=30
84 | local scale_type="any-jail"
85 | local scale_time=15
86 |
87 | while getopts ":m:M:r:S:t:T:" _o; do
88 | case "${_o}" in
89 | m)
90 | min="${OPTARG}"
91 | ;;
92 | M)
93 | max="${OPTARG}"
94 | ;;
95 | r)
96 | rctl_rules="${OPTARG}"
97 | ;;
98 | S)
99 | stabilization_window="${OPTARG}"
100 | ;;
101 | t)
102 | scale_type="${OPTARG}"
103 | ;;
104 | T)
105 | scale_time="${OPTARG}"
106 | ;;
107 | *)
108 | usage
109 | ;;
110 | esac
111 | done
112 | shift $((OPTIND-1))
113 |
114 | if ! checknumber "${min}"; then
115 | err "${min}: invalid number."
116 | return ${EX_DATAERR}
117 | fi
118 |
119 | if [ ${min} -lt 1 ]; then
120 | err "${min}: too low number."
121 | return ${EX_DATAERR}
122 | fi
123 |
124 | if ! checknumber "${max}"; then
125 | err "${max}: invalid number."
126 | return ${EX_DATAERR}
127 | fi
128 |
129 | if [ ${max} -lt 0 ]; then
130 | err "${max}: too low number."
131 | return ${EX_DATAERR}
132 | fi
133 |
134 | if [ ${max} -ne 0 ] && [ ${max} -lt ${min} ]; then
135 | err "${max} < ${min}: the maximum number of replicas cannot be less than the minimum."
136 | return ${EX_DATAERR}
137 | fi
138 |
139 | if ! checknumber "${scale_time}"; then
140 | err "${scale_time}: invalid number."
141 | return ${EX_DATAERR}
142 | fi
143 |
144 | if [ ${scale_time} -lt 0 ]; then
145 | err "${scale_time}: too low number."
146 | return ${EX_DATAERR}
147 | fi
148 |
149 | if ! checknumber "${stabilization_window}"; then
150 | err "${stabilization_window}: invalid number."
151 | return ${EX_DATAERR}
152 | fi
153 |
154 | if [ ${stabilization_window} -lt 0 ]; then
155 | err "${stabilization_window}: too low number."
156 | return ${EX_DATAERR}
157 | fi
158 |
159 | case "${scale_type}" in
160 | any-jail|any-project|average|percent-jail=*|percent-project=*) ;;
161 | *) err "${scale_type}: invalid scale type."; return ${EX_DATAERR} ;;
162 | esac
163 |
164 | local nodes
165 | nodes=`jet get-nodes "${project}"` || return $?
166 |
167 | if [ -z "${nodes}" ]; then
168 | return ${EX_CANTCREAT}
169 | fi
170 |
171 | if [ ${max} -eq 0 ]; then
172 | max=`echo -e "${nodes}" | wc -l | tr -d ' '`
173 | fi
174 |
175 | local good_count=0 good_list=
176 | local bad_count=0 bad_list=
177 |
178 | local errlevel
179 |
180 | local node
181 | for node in ${nodes}; do
182 | info "Checking health of project '${project}' on node '${node}'"
183 |
184 | errlevel=`checkprojecthealth "${project}" "${node}"`
185 |
186 | errlevel=$?
187 |
188 | if [ ${errlevel} -eq 0 ]; then
189 | good_count=$((good_count+1))
190 |
191 | if [ -z "${good_list}" ]; then
192 | good_list="${node}"
193 | else
194 | good_list="${good_list} ${node}"
195 | fi
196 | elif [ ${errlevel} -eq 1 ]; then
197 | # Nodes with which we can interact but whose project is in poor condition.
198 |
199 | bad_count=$((bad_count+1))
200 |
201 | if [ -z "${bad_list}" ]; then
202 | bad_list="${node}"
203 | else
204 | bad_list="${bad_list} ${node}"
205 | fi
206 | else
207 | #
208 | # The Good, the Bad and the Ugly.
209 | #
210 | # Ignore nodes that we cannot interact with.
211 | #
212 | fi
213 | done
214 |
215 | # We need more replicas!
216 | if [ ${good_count} -lt ${min} ]; then
217 | if [ ${bad_count} -eq 0 ]; then
218 | err "Insufficient nodes to replicate the project (${good_count}/${min})!"
219 | return ${EX_UNAVAILABLE}
220 | fi
221 |
222 | local count=0
223 |
224 | local total
225 | total=$((min-good_count))
226 |
227 | info "Scaling (${good_count}/${min})"
228 |
229 | nodes=
230 |
231 | for node in ${bad_list}; do
232 | count=$((count+1))
233 |
234 | if [ -z "${nodes}" ]; then
235 | nodes="${node}"
236 | else
237 | nodes="${nodes} ${node}"
238 | fi
239 |
240 | if [ ${count} -ge ${total} ]; then
241 | info "Done (${count}/${total})"
242 | break
243 | fi
244 | done
245 |
246 | info "Deploying '${project}' on nodes '${nodes}'"
247 |
248 | parallel_deploy "${project}" "${nodes}"
249 |
250 | info "Checking again"
251 |
252 | # Repeat the process...
253 | main "$@"
254 | return $?
255 | fi
256 |
257 | if [ -z "${rctl_rules}" ]; then
258 | debug "There are no rctl(8) rules, exiting"
259 | return ${EX_OK}
260 | fi
261 |
262 | info "Processing rctl(8) rules"
263 |
264 | local deploy=false
265 |
266 | for node in ${good_list}; do
267 | check_rctl "${project}" "${node}" "${scale_type}" "${rctl_rules}"
268 |
269 | errlevel=$?
270 |
271 | if [ ${errlevel} -eq 1 ]; then
272 | deploy=true
273 |
274 | warn "Project '${project}' has not passed '${scale_type}:(${rctl_rules})' on node '${node}'"
275 |
276 | break
277 | elif [ ${errlevel} -eq 2 ]; then
278 | continue # error.
279 | fi
280 | done
281 |
282 | if [ ${good_count} -ge ${max} ]; then
283 | info "The maximum number of good nodes has been reached (${max})."
284 | elif ${deploy}; then
285 | if [ ${bad_count} -eq 0 ]; then
286 | warn "Insufficient nodes to replicate the project (${bad_count})!"
287 | return ${EX_OK}
288 | fi
289 |
290 | info "Scaling"
291 |
292 | node=`printf "%s" "${bad_list}" | tr ' ' '\n' | sort -R | head -1`
293 |
294 | deploy "${project}" "${node}"
295 |
296 | if [ ${scale_time} -gt 0 ]; then
297 | info "Sleeping ${scale_time}"
298 | sleep "${scale_time}" || return $?
299 | fi
300 |
301 | info "Checking again"
302 |
303 | main "$@"
304 | return $?
305 | fi
306 |
307 | if ! ${deploy} && [ ${good_count} -gt ${min} ]; then
308 | warn "Resource usage appears to be decreasing, destroying nodes"
309 |
310 | for node in ${good_list}; do
311 | destroy "${project}" "${node}"
312 |
313 | info "Done (${good_count}/${min})"
314 |
315 | good_count=$((good_count-1))
316 |
317 | if [ ${good_count} -le ${min} ]; then
318 | break
319 | fi
320 |
321 | if [ ${stabilization_window} -gt 0 ]; then
322 | info "Sleeping ${stabilization_window}"
323 | sleep "${stabilization_window}" || return $?
324 | fi
325 | done
326 |
327 | info "Checking again"
328 |
329 | main "$@"
330 | return $?
331 | fi
332 |
333 | exit ${EX_OK}
334 | }
335 |
336 | check_rctl()
337 | {
338 | local project
339 | project="$1"
340 |
341 | local node
342 | node="$2"
343 |
344 | local scale_type
345 | scale_type="$3"
346 |
347 | local rctl_rules
348 | rctl_rules="$4"
349 |
350 | local errlevel
351 |
352 | for rctl_rule in ${rctl_rules}; do
353 | if [ -z "${rctl_rule}" ]; then
354 | continue
355 | fi
356 |
357 | if ! _check_rctl_rule "${rctl_rule}"; then
358 | warn "${rctl_rule}: invalid rctl(8) rule syntax."
359 | continue
360 | fi
361 |
362 | local resource
363 | resource=`printf "%s" "${rctl_rule}" | cut -d= -f1`
364 |
365 | local value
366 | value=`printf "%s" "${rctl_rule}" | cut -d= -f2-`
367 |
368 | case "${resource}" in
369 | datasize|stacksize|coredumpsize|memoryuse|memorylocked|vmemoryuse|swapuse|msgqsize|shmsize|readbps|writebps)
370 | value=`humanize_number "${value}"`
371 |
372 | if [ $? -ne 0 ]; then
373 | warn "${value}: invalid value for '${resource}'"
374 | continue
375 | fi
376 | ;;
377 | cputime|maxproc|openfiles|pseudoterminals|nthr|msgqqueued|nmsgq|nsem|nsemop|nshm|wallclock|pcpu|readiops|writeiops)
378 | if ! checknumber "${value}"; then
379 | warn "${value}: invalid number for '${resource}'."
380 | continue
381 | fi
382 | ;;
383 | *)
384 | warn "${resource}: resource not found."
385 | continue
386 | ;;
387 | esac
388 |
389 | if [ ${value} -le 0 ]; then
390 | warn "${value}: too low number for '${resource}'."
391 | continue
392 | fi
393 |
394 | case "${scale_type}" in
395 | any-jail|any-project|average)
396 | scale_${scale_type} "${project}" "${node}" "${resource}" "${value}"
397 |
398 | errlevel=$?
399 |
400 | if [ ${errlevel} -ne 0 ]; then
401 | return ${errlevel}
402 | fi
403 | ;;
404 | percent-jail=*|percent-project=*)
405 | local percent
406 | percent=`printf "%s" "${scale_type}" | cut -s -d= -f2-`
407 |
408 | scale_type=`printf "%s" "${scale_type}" | cut -s -d= -f1`
409 |
410 | if [ -z "${percent}" ]; then
411 | warn "Invalid percent number for '${resource}'."
412 | continue
413 | fi
414 |
415 | if ! checknumber "${percent}"; then
416 | warn "${percent}: invalid number for '${resource}'."
417 | continue
418 | fi
419 |
420 | if [ ${percent} -lt 1 -o ${percent} -gt 100 ]; then
421 | warn "${percent}: invalid range for '${resource}': must be between 1 and 100."
422 | continue
423 | fi
424 |
425 | scale_${scale_type} "${project}" "${node}" "${percent}" "${resource}" "${value}"
426 |
427 | errlevel=$?
428 |
429 | if [ ${errlevel} -ne 0 ]; then
430 | return ${errlevel}
431 | fi
432 | ;;
433 | esac
434 | done
435 |
436 | return ${EX_OK}
437 | }
438 |
439 | scale_any-jail()
440 | {
441 | local project
442 | project="$1"
443 |
444 | local node
445 | node="$2"
446 |
447 | local resource
448 | resource="$3"
449 |
450 | local value
451 | value="$4"
452 |
453 | local jails
454 | jails=`_get_jails "${project}" "${node}"` || return $?
455 |
456 | local jail
457 | for jail in ${jails}; do
458 | local errlevel
459 |
460 | local stat
461 | stat=`_get_rctl_stat "${node}" "${jail}" "${resource}"`
462 |
463 | errlevel=$?
464 |
465 | if [ ${errlevel} -ne 0 ]; then
466 | continue
467 | fi
468 |
469 | if [ ${stat} -ge ${value} ]; then
470 | debug "Comparing '${resource}' on node '${node}': current:${stat} >= limit:${value} = true"
471 |
472 | return 1
473 | else
474 | debug "Comparing '${resource}' on node '${node}': current:${stat} >= limit:${value} = false"
475 | fi
476 | done
477 |
478 | return 0
479 | }
480 |
481 | scale_any-project()
482 | {
483 | local project
484 | project="$1"
485 |
486 | local node
487 | node="$2"
488 |
489 | local resource
490 | resource="$3"
491 |
492 | local value
493 | value="$4"
494 |
495 | local jails
496 | jails=`_get_jails "${project}" "${node}"` || return $?
497 |
498 | local total=0
499 |
500 | local jail
501 | for jail in ${jails}; do
502 | local errlevel
503 |
504 | local stat
505 | stat=`_get_rctl_stat "${node}" "${jail}" "${resource}" 2>&1`
506 |
507 | errlevel=$?
508 |
509 | if [ ${errlevel} -ne 0 ]; then
510 | continue
511 | fi
512 |
513 | total=$((total+stat))
514 | done
515 |
516 | if [ ${total} -ge ${value} ]; then
517 | debug "Comparing '${resource}' on node '${node}': current:${total} >= limit:${value} = true"
518 |
519 | return 1
520 | else
521 | debug "Comparing '${resource}' on node '${node}': current:${total} >= limit:${value} = false"
522 |
523 | return 0
524 | fi
525 | }
526 |
527 | scale_average()
528 | {
529 | local project
530 | project="$1"
531 |
532 | local node
533 | node="$2"
534 |
535 | local resource
536 | resource="$3"
537 |
538 | local value
539 | value="$4"
540 |
541 | local jails
542 | jails=`_get_jails "${project}" "${node}"` || return $?
543 |
544 | local count=0
545 | local total=0
546 |
547 | local jail
548 | for jail in ${jails}; do
549 | local errlevel
550 |
551 | local stat
552 | stat=`_get_rctl_stat "${node}" "${jail}" "${resource}" 2>&1`
553 |
554 | errlevel=$?
555 |
556 | if [ ${errlevel} -ne 0 ]; then
557 | continue
558 | fi
559 |
560 | count=$((count+1))
561 | total=$((total+stat))
562 | done
563 |
564 | local result
565 | result=$((total / count))
566 |
567 | if [ ${result} -ge ${value} ]; then
568 | debug "Comparing '${resource}' on node '${node}': current:${result} >= limit:${value} = true"
569 |
570 | return 1
571 | else
572 | debug "Comparing '${resource}' on node '${node}': current:${result} >= limit:${value} = false"
573 |
574 | return 0
575 | fi
576 | }
577 |
578 | scale_percent-jail()
579 | {
580 | local project
581 | project="$1"
582 |
583 | local node
584 | node="$2"
585 |
586 | local percent
587 | percent="$3"
588 |
589 | local resource
590 | resource="$4"
591 |
592 | local value
593 | value="$5"
594 |
595 | local jails
596 | jails=`_get_jails "${project}" "${node}"` || return $?
597 |
598 | local limit
599 | limit=$(((value * percent) / 100))
600 |
601 | local jail
602 | for jail in ${jails}; do
603 | local errlevel
604 |
605 | local stat
606 | stat=`_get_rctl_stat "${node}" "${jail}" "${resource}" 2>&1`
607 |
608 | errlevel=$?
609 |
610 | if [ ${errlevel} -ne 0 ]; then
611 | continue
612 | fi
613 |
614 | if [ ${stat} -ge ${limit} ]; then
615 | debug "Comparing '${resource}' on node '${node}': current:${stat} >= limit:${limit} = true"
616 |
617 | return 1
618 | else
619 | debug "Comparing '${resource}' on node '${node}': current:${stat} >= limit:${limit} = false"
620 | fi
621 | done
622 |
623 | return 0
624 | }
625 |
626 | scale_percent-project()
627 | {
628 | local project
629 | project="$1"
630 |
631 | local node
632 | node="$2"
633 |
634 | local percent
635 | percent="$3"
636 |
637 | local resource
638 | resource="$4"
639 |
640 | local value
641 | value="$5"
642 |
643 | local jails
644 | jails=`_get_jails "${project}" "${node}"` || return $?
645 |
646 | local total=0
647 |
648 | local limit
649 | limit=$(((value * percent) / 100))
650 |
651 | local jail
652 | for jail in ${jails}; do
653 | local errlevel
654 |
655 | local stat
656 | stat=`_get_rctl_stat "${node}" "${jail}" "${resource}" 2>&1`
657 |
658 | errlevel=$?
659 |
660 | if [ ${errlevel} -ne 0 ]; then
661 | continue
662 | fi
663 |
664 | total=$((total+stat))
665 | done
666 |
667 | if [ ${total} -ge ${limit} ]; then
668 | debug "Comparing '${resource}' on node '${node}': current:${total} >= limit:${limit} = true"
669 |
670 | return 1
671 | else
672 | debug "Comparing '${resource}' on node '${node}': current:${total} >= limit:${limit} = false"
673 |
674 | return 0
675 | fi
676 | }
677 |
678 | _get_jails()
679 | {
680 | local project
681 | project="$1"
682 |
683 | local node
684 | node="$2"
685 |
686 | local errlevel
687 |
688 | local project_info
689 | project_info=`run_director "${project}" "${node}" "NO" "NO" describe 2>&1`
690 |
691 | errlevel=$?
692 |
693 | if [ ${errlevel} -ne 0 ]; then
694 | warn "Could not get information from project '${project}': ${project_info}."
695 | return ${EX_SOFTWARE}
696 | fi
697 |
698 | local jails
699 | jails=`echo -e "${project_info}" | jq -r '.services.[].jail' 2>&1`
700 |
701 | errlevel=$?
702 |
703 | if [ ${errlevel} -ne 0 ]; then
704 | warn "Could not get jails from project '${project}': ${jails}"
705 | return ${EX_SOFTWARE}
706 | fi
707 |
708 | echo "${jails}"
709 |
710 | return ${EX_OK}
711 | }
712 |
713 | _get_rctl_stat()
714 | {
715 | local node
716 | node="$1"
717 |
718 | local jail
719 | jail="$2"
720 |
721 | local resource
722 | resource="$3"
723 |
724 | local errlevel
725 |
726 | local current
727 | current=`remote_exc "${node}" "NO" "NO" appjail limits stats -epHIth -- "${jail}" "${resource}" 2>&1`
728 |
729 | errlevel=$?
730 |
731 | if [ -n "${current}" ] && [ ${errlevel} -eq 0 ]; then
732 | echo "${current}"
733 | return ${EX_OK}
734 | else
735 | warn "Could not get metric '${resource}' from jail '${jail}' on node '${node}': ${current}"
736 | return ${EX_SOFTWARE}
737 | fi
738 | }
739 |
740 | _check_rctl_rule()
741 | {
742 | local rctl_rule
743 | rctl_rule="$1"
744 |
745 | if printf "%s" "${rctl_rule}" | grep -qEe '^[a-z]+=[0-9]+[kKmMgGtTpPeE]?$'; then
746 | return 0
747 | else
748 | return 1
749 | fi
750 | }
751 |
752 | usage()
753 | {
754 | err "usage: ${DEPLOY_NAME}: [-m ] [-M ] [-r ] [-S ]"
755 | err " [-t ] [-T ]"
756 | exit ${EX_USAGE}
757 | }
758 |
759 | main "$@"
760 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/deploy.single:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="deploy.single"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local project
55 | project="${LITTLEJET_PROJECT}"
56 |
57 | if [ -z "${project}" ]; then
58 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
59 | exit ${EX_CONFIG}
60 | fi
61 |
62 | if ! checkprojectname "${project}"; then
63 | err "${project}: invalid project name."
64 | exit ${EX_DATAERR}
65 | fi
66 |
67 | if ! checkproject "${project}"; then
68 | err "${project}: project cannot be found."
69 | exit ${EX_NOINPUT}
70 | fi
71 |
72 | local node
73 | node="$1"
74 |
75 | if [ -z "${node}" ]; then
76 | usage
77 | fi
78 |
79 | if ! checknodename "${node}"; then
80 | err "${node}: invalid node name."
81 | exit ${EX_NOINPUT}
82 | fi
83 |
84 | if ! checknode "${project}" "${node}"; then
85 | err "${node}: node cannot be found."
86 | exit ${EX_NOINPUT}
87 | fi
88 |
89 | deploy "${project}" "${node}"
90 |
91 | exit ${EX_OK}
92 | }
93 |
94 | usage()
95 | {
96 | err "usage: ${DEPLOY_NAME} "
97 | exit ${EX_USAGE}
98 | }
99 |
100 | main "$@"
101 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.client:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.client"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | checkdependency jq
55 |
56 | local project
57 | project="${LITTLEJET_PROJECT}"
58 |
59 | if [ -z "${project}" ]; then
60 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
61 | exit ${EX_CONFIG}
62 | fi
63 |
64 | if ! checkprojectname "${project}"; then
65 | err "${project}: invalid project name."
66 | exit ${EX_DATAERR}
67 | fi
68 |
69 | if ! checkproject "${project}"; then
70 | err "${project}: project cannot be found."
71 | exit ${EX_NOINPUT}
72 | fi
73 |
74 | atexit_init
75 | atexit_add ". \"${lib_subr}\""
76 | atexit_add "setname \"${DEPLOY_NAME}\""
77 | atexit_add "load_config"
78 |
79 | local _o
80 | local jail="littlejet-server-wg"
81 | local node=
82 | local suffix="-wg"
83 | local virtual_network=
84 |
85 | while getopts ":j:n:s:v:" _o; do
86 | case "${_o}" in
87 | j)
88 | jail="${OPTARG}"
89 | ;;
90 | n)
91 | node="${OPTARG}"
92 | ;;
93 | s)
94 | suffix="${OPTARG}"
95 | ;;
96 | v)
97 | virtual_network="${OPTARG}"
98 | ;;
99 | *)
100 | usage
101 | ;;
102 | esac
103 | done
104 | shift $((OPTIND-1))
105 |
106 | local vpn
107 | vpn="$1"
108 |
109 | if [ -z "${vpn}" ]; then
110 | usage
111 | fi
112 |
113 | shift
114 |
115 | if ! checknodename "${vpn}"; then
116 | err "${vpn}: invalid node name."
117 | exit ${EX_NOINPUT}
118 | fi
119 |
120 | local errlevel
121 |
122 | local output
123 | output=`testnode "${vpn}" 2>&1`
124 |
125 | errlevel=$?
126 |
127 | if [ ${errlevel} -ne 0 ]; then
128 | err "Could not deploy VPN client due to an error on node '${vpn}': ${output}"
129 | exit ${EX_SOFTWARE}
130 | fi
131 |
132 | output=`remote_exc "${vpn}" "NO" "NO" appjail status -q -- "\"${jail}\"" 2>&1`
133 |
134 | errlevel=$?
135 |
136 | if [ ${errlevel} -eq 0 ]; then
137 | # pass
138 | elif [ ${errlevel} -eq 1 ]; then
139 | warn "VPN server on jail '${jail}', node '${vpn}', hasn't been started"
140 |
141 | remote_exc "${vpn}" "YES" "NO" appjail start -- "\"${jail}\""
142 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
143 | err "VPN server doesn't exist, use 'vpn.wg.server' to deploy it."
144 | exit ${EX_NOINPUT}
145 | else
146 | err "Could not check status of jail '${jail}' on node '${vpn}': ${output}"
147 | exit ${EX_SOFTWARE}
148 | fi
149 |
150 | output=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" test -f /.done 2>&1`
151 |
152 | errlevel=$?
153 |
154 | if [ ${errlevel} -eq 0 ]; then
155 | # pass
156 | elif [ ${errlevel} -eq 1 ]; then
157 | err "The VPN server on jail '${jail}' on node '${vpn}' does not appear to be healthy, make sure you deploy it correctly."
158 | exit ${EX_SOFTWARE}
159 | else
160 | err "Failed to check the status of jail '${jail}' on node '${vpn}': ${output}"
161 | exit ${EX_SOFTWARE}
162 | fi
163 |
164 | local network_address
165 | network_address=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh get-network-addr 2>&1`
166 |
167 | if [ $? -ne 0 ]; then
168 | err "Failed to get network address on node '${vpn}': ${network_address}"
169 | exit ${EX_SOFTWARE}
170 | fi
171 |
172 | local allow_exclude=true
173 | local nodes
174 |
175 | if [ -n "${node}" ]; then
176 | if ! checknodename "${node}"; then
177 | err "${node}: invalid node name."
178 | exit ${EX_NOINPUT}
179 | fi
180 |
181 | if ! checknode "${node}"; then
182 | err "${node}: node cannot be found."
183 | exit ${EX_NOINPUT}
184 | fi
185 |
186 | nodes="${node}"
187 | allow_exclude=false
188 | else
189 | nodes=`jet get-nodes "${project}"` || exit $?
190 |
191 | if [ -z "${nodes}" ]; then
192 | exit ${EX_CANTCREAT}
193 | fi
194 | fi
195 |
196 | for node in ${nodes}; do
197 | if ${allow_exclude}; then
198 | local exclude
199 | exclude=`jet get-label "${project}" "${node}" vpn.wg.client.exclude 2> /dev/null`
200 |
201 | if [ -n "${exclude}" ]; then
202 | debug "Node '${node}' was excluded"
203 | continue
204 | fi
205 | fi
206 |
207 | local errlevel
208 |
209 | local output
210 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
211 |
212 | errlevel=$?
213 |
214 | if [ ${errlevel} -eq 0 ]; then
215 | # pass
216 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
217 | warn "${project}: project not found"
218 | continue
219 | else
220 | warn "Project '${project}' has an error: ${output}"
221 | continue
222 | fi
223 |
224 | local project_info
225 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe 2>&1`
226 |
227 | if [ $? -ne 0 ]; then
228 | warn "Error getting information about project '${project}' on node '${node}': ${project_info}"
229 | continue
230 | fi
231 |
232 | local state
233 | state=`echo -e "${project_info}" | safe_exc jq -r .state 2>&1`
234 |
235 | if [ $? -ne 0 ]; then
236 | warn "Error parsing information about project '${project}' on node '${node}': ${state}"
237 | continue
238 | fi
239 |
240 | debug "Project state is '${state}'"
241 |
242 | if ! [ "${state}" = "DONE" -o "${state}" = "DESTROYING" ]; then
243 | warn "State (${state}) is different than expected"
244 | continue
245 | fi
246 |
247 | local services_status
248 | services_status=`echo -e "${project_info}" | safe_exc jq -r '.services.[].status' 2>&1`
249 |
250 | if [ $? -ne 0 ]; then
251 | warn "Error parsing information about project '${project}' on node '${node}': ${services_status}"
252 | continue
253 | fi
254 |
255 | local service_index=0
256 |
257 | local service_status
258 | for service_status in ${services_status}; do
259 | local service_name
260 | service_name=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].name" 2>&1`
261 |
262 | if [ $? -ne 0 ]; then
263 | warn "Error parsing information about project '${project}' on node '${node}': ${service_name}"
264 | service_index=$((service_index+1))
265 | continue
266 | fi
267 |
268 | local peerid
269 | peerid="peer://${node}/${project}/${service_name}"
270 |
271 | local is_created=false
272 |
273 | remote_exc "${vpn}" "NO" "NO" \
274 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh check "${peerid}"
275 |
276 | errlevel=$?
277 |
278 | if [ ${errlevel} -eq ${EX_OK} ]; then
279 | is_created=true
280 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
281 | is_created=false
282 | else
283 | warn "Error checking for existence of peer '${peerid}'"
284 | service_index=$((service_index+1))
285 | continue
286 | fi
287 |
288 | local vpn_jail
289 | vpn_jail="${project}-${service_name}${suffix}"
290 | vpn_jail=`printf "%s" "${vpn_jail}" | sed -Ee 's/\./_0X2E_/g'`
291 |
292 | if [ ${service_status} -eq 0 -o "${service_status}" -eq 1 ]; then
293 | if ! ${is_created}; then
294 | remote_exc "${vpn}" "NO" "NO" \
295 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh add "${peerid}"
296 |
297 | if [ $? -ne 0 ]; then
298 | warn "Error creating peer '${peerid}'"
299 | service_index=$((service_index+1))
300 | continue
301 | fi
302 | fi
303 | elif [ ${service_status} -eq ${EX_NOINPUT} ]; then
304 | warn "Service '${service_name}' hasn't been created"
305 |
306 | if ${is_created}; then
307 | warn "Destroying peer '${peerid}'"
308 |
309 | remote_exc "${vpn}" "NO" "NO" \
310 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh del "${peerid}"
311 |
312 | if [ $? -ne 0 ]; then
313 | warn "Error destroying peer '${peerid}'"
314 | fi
315 | fi
316 |
317 | output=`remote_exc "${node}" "NO" "NO" appjail status -q -- "\"${vpn_jail}\"" 2>&1`
318 |
319 | errlevel=$?
320 |
321 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
322 | if [ ${errlevel} -eq 0 ]; then
323 | warn "Stopping VPN jail '${vpn_jail}' on peer '${peerid}'"
324 |
325 | remote_exc "${node}" "NO" "NO" \
326 | appjail stop -- "\"${vpn_jail}\""
327 | fi
328 |
329 | warn "Destroying VPN client on peer '${peerid}'"
330 |
331 | remote_exc "${node}" "NO" "NO" \
332 | appjail jail destroy -Rf -- "\"${vpn_jail}\""
333 |
334 | if [ $? -ne 0 ]; then
335 | warn "Error destroying VPN client on peer '${peerid}'"
336 | fi
337 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
338 | # pass
339 | else
340 | warn "Could not check status of jail '${jail}' on node '${vpn_jail}': ${output}"
341 | fi
342 |
343 | service_index=$((service_index+1))
344 | continue
345 | else
346 | warn "Service '${service_name}' has a different status (${service_status}) than expected"
347 | service_index=$((service_index+1))
348 | continue
349 | fi
350 |
351 | local service_jail
352 | service_jail=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].jail" 2>&1`
353 |
354 | if [ $? -ne 0 ]; then
355 | warn "Error parsing information about project '${project}' on node '${node}': ${service_jail}"
356 | service_index=$((service_index+1))
357 | continue
358 | fi
359 |
360 | local labels
361 | labels="from.port include.me proto target.port"
362 |
363 | local label_from_port=
364 | local label_include_me=
365 | local label_proto="tcp"
366 | local label_target_port=
367 |
368 | local label
369 | for label in ${labels}; do
370 | label="vpn.wg.client.${label}"
371 |
372 | local value
373 | value=`remote_exc "${node}" "NO" "NO" appjail label get -I -l "${label}" -- "${service_jail}" value 2>&1`
374 |
375 | errlevel=$?
376 |
377 | if [ ${errlevel} -eq 0 ]; then
378 | # pass
379 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
380 | continue
381 | else
382 | warn "Error getting label '${label}' from service '${service_name}': ${value}"
383 | continue
384 | fi
385 |
386 | case "${label}" in
387 | *.from.port)
388 | setvar "label_from_port" "${value}"
389 | ;;
390 | *.include.me)
391 | setvar "label_include_me" "${value}"
392 | ;;
393 | *.proto)
394 | setvar "label_proto" "${value}"
395 | ;;
396 | *.target.port)
397 | setvar "label_target_port" "${value}"
398 | ;;
399 | esac
400 | done
401 |
402 | if [ -z "${label_include_me}" ]; then
403 | debug "Service '${service_name}' doesn't have the label 'vpn.wg.client.include.me'"
404 | service_index=$((service_index+1))
405 | continue
406 | fi
407 |
408 | if [ -z "${label_from_port}" ]; then
409 | warn "Service '${service_name}' wants to be included in the list but has not defined an external port"
410 | service_index=$((service_index+1))
411 | continue
412 | fi
413 |
414 | if ! checknumber "${label_from_port}" || ! checkport "${label_from_port}"; then
415 | want "Service '${service_name}' has an invalid external port: ${label_from_port}"
416 | service_index=$((service_index+1))
417 | continue
418 | fi
419 |
420 | if [ -n "${label_target_port}" ]; then
421 | if ! checknumber "${label_target_port}" || ! checkport "${label_target_port}"; then
422 | want "Service '${service_name}' has an invalid target port: ${label_target_port}"
423 | service_index=$((service_index+1))
424 | continue
425 | fi
426 | else
427 | label_target_port="${label_from_port}"
428 | fi
429 |
430 | case "${label_proto}" in
431 | tcp|udp)
432 | ;;
433 | *)
434 | warn "Service '${service_name}' has an invalid protocol: ${label_proto}"
435 | service_index=$((service_index+1))
436 | continue
437 | ;;
438 | esac
439 |
440 | set --
441 | set -- --wg_network_address "${network_address}"
442 | set -- "$@" --wg_jail "${service_jail}"
443 | set -- "$@" --wg_from_port "${label_from_port}"
444 | set -- "$@" --wg_target_port "${label_target_port}"
445 | set -- "$@" --wg_proto "${label_proto}"
446 |
447 | if [ -n "${virtual_network}" ]; then
448 | set -- "$@" --wg_virtualnet "${virtual_network}"
449 | fi
450 |
451 | output=`remote_exc "${node}" "NO" "NO" appjail status -q -- "\"${vpn_jail}\"" 2>&1`
452 |
453 | errlevel=$?
454 |
455 | local deploy_vpn_client=false
456 |
457 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
458 | if [ ${errlevel} -eq 1 ]; then
459 | warn "VPN client on jail '${vpn_jail}', node '${node}', hasn't been started"
460 |
461 | remote_exc "${node}" "NO" "NO" appjail start -- "\"${vpn_jail}\""
462 |
463 | if [ $? -ne 0 ]; then
464 | warn "Error starting jail '${vpn_jail}'"
465 | service_index=$((service_index+1))
466 | continue
467 | fi
468 | fi
469 |
470 | output=`remote_exc "${node}" "NO" "NO" appjail cmd jexec "\"${vpn_jail}\"" test -f /.done 2>&1`
471 |
472 | errlevel=$?
473 |
474 | if [ ${errlevel} -eq 0 ]; then
475 | # pass
476 | elif [ ${errlevel} -eq 1 ]; then
477 | warn "VPN client on jail '${vpn_jail}' on node '${node}' does not appear to be healthy, re-deploying"
478 |
479 | deploy_vpn_client=true
480 | else
481 | warn "Failed to check the status of jail '${vpn_jail}' on node '${node}': ${output}"
482 | service_index=$((service_index+1))
483 | continue
484 | fi
485 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
486 | info "Creating VPN client on jail '${vpn_jail}', node '${node}'"
487 |
488 | deploy_vpn_client=true
489 | else
490 | warn "Could not check status of jail '${jail}' on node '${vpn_jail}': ${output}"
491 | service_index=$((service_index+1))
492 | continue
493 | fi
494 |
495 | if ${deploy_vpn_client}; then
496 | if ! deploy_vpn_client "${node}" "${peerid}" "${jail}" "${vpn}" "${vpn_jail}" "$@"; then
497 | service_index=$((service_index+1))
498 | continue
499 | fi
500 | fi
501 |
502 | local current_network_ip4
503 | current_network_ip4=`remote_exc "${node}" "NO" "NO" appjail jail get -I -- "\"${service_jail}\"" network_ip4 2>&1`
504 |
505 | if [ $? -eq 0 ]; then
506 | if [ -z "${current_network_ip4}" ]; then
507 | warn "Service '${service_name}' doesn't have an IPv4 address assigned"
508 | service_index=$((service_index+1))
509 | continue
510 | fi
511 |
512 | local old_network_ip4
513 | old_network_ip4=`remote_exc "${node}" "NO" "NO" appjail cmd jexec "\"${vpn_jail}\"" head -1 -- "/.jail.address" 2>&1`
514 |
515 | if [ $? -ne 0 ]; then
516 | warn "Error getting IPv4 from VPN client '${vpn_jail}': ${old_network_ip4}"
517 | service_index=$((service_index+1))
518 | continue
519 | fi
520 |
521 | if [ "${current_network_ip4}" != "${old_network_ip4}" ]; then
522 | info "Updating IPv4 on VPN client '${vpn_jail}': ${current_network_ip4} != ${old_network_ip4}"
523 |
524 | remote_exc "${node}" "NO" "NO" \
525 | appjail cmd jexec "\"${vpn_jail}\"" sh -c "\"echo -n ${current_network_ip4} > /.jail.address\""
526 |
527 | if [ $? -ne 0 ]; then
528 | warn "Error updating IPv4 on VPN client '${vpn_jail}'"
529 | service_index=$((service_index+1))
530 | continue
531 | fi
532 |
533 | remote_exc "${node}" "NO" "NO" \
534 | appjail cmd jexec "\"${vpn_jail}\"" service pf reload
535 |
536 | if [ $? -ne 0 ]; then
537 | warn "Error reloading rules in pf from VPN client '${vpn_jail}'"
538 | fi
539 | fi
540 | else
541 | warn "Error getting IPv4 from service '${service_name}': ${current_network_ip4}"
542 | fi
543 |
544 | service_index=$((service_index+1))
545 | done
546 | done
547 |
548 | exit ${EX_OK}
549 | }
550 |
551 | deploy_vpn_client()
552 | {
553 | local node
554 | node="$1"
555 |
556 | local peerid
557 | peerid="$2"
558 |
559 | local jail
560 | jail="$3"
561 |
562 | local vpn
563 | vpn="$4"
564 |
565 | local vpn_jail
566 | vpn_jail="$5"
567 |
568 | shift 5
569 |
570 | local local_wg_conf
571 | local_wg_conf=`tempfile`
572 |
573 | if [ $? -ne 0 ]; then
574 | warn "Error creating a temporary file locally: ${local_wg_conf}"
575 | return 1
576 | fi
577 |
578 | local wg_conf
579 | wg_conf=`remote_exc "${node}" "NO" "NO" mktemp -t littlejet-vpn.wg.client 2>&1`
580 |
581 | if [ $? -ne 0 ]; then
582 | warn "Error while remotely creating a temporary file: ${wg_conf}"
583 | return 1
584 | fi
585 |
586 | remote_exc "${vpn}" "NO" "NO" \
587 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh show "${peerid}" > "${local_wg_conf}"
588 |
589 | if [ $? -ne 0 ]; then
590 | warn "Error writing locally the WireGuard configuration file of peer '${peerid}'"
591 | remote_exc "${node}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
592 | return 1
593 | fi
594 |
595 | (mirror "${local_wg_conf}" "${node}:${wg_conf}")
596 |
597 | if [ $? -ne 0 ]; then
598 | warn "Error uploading the WireGuard configuration file to peer '${peerid}'"
599 | remote_exc "${node}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
600 | return 1
601 | fi
602 |
603 | atexit_add "removefile \"${local_wg_conf}\""
604 |
605 | removefile "${local_wg_conf}"
606 |
607 | set -- "$@" --wg_conf "\"${wg_conf}\""
608 |
609 | remote_exc "${node}" "NO" "NO" \
610 | appjail makejail \
611 | -j "\"${vpn_jail}\"" \
612 | -f "\"gh+DtxdF/LittleJet-wg-makejail --file client.makejail\"" \
613 | -- "$@"
614 |
615 | if [ $? -ne 0 ]; then
616 | warn "Error deploying VPN client on peer '${peerid}'"
617 | remote_exc "${node}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
618 | return 1
619 | fi
620 |
621 | remote_exc "${node}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
622 |
623 | return 0
624 | }
625 |
626 | usage()
627 | {
628 | err "usage: ${DEPLOY_NAME} [-j ] [-n ] [-s ] [-v ] "
629 | exit ${EX_USAGE}
630 | }
631 |
632 | main "$@"
633 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.client.destroy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.client.destroy"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | checkdependency jq
55 |
56 | local project
57 | project="${LITTLEJET_PROJECT}"
58 |
59 | if [ -z "${project}" ]; then
60 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
61 | exit ${EX_CONFIG}
62 | fi
63 |
64 | if ! checkprojectname "${project}"; then
65 | err "${project}: invalid project name."
66 | exit ${EX_DATAERR}
67 | fi
68 |
69 | if ! checkproject "${project}"; then
70 | err "${project}: project cannot be found."
71 | exit ${EX_NOINPUT}
72 | fi
73 |
74 | atexit_init
75 | atexit_add ". \"${lib_subr}\""
76 | atexit_add "setname \"${DEPLOY_NAME}\""
77 | atexit_add "load_config"
78 |
79 | local _o
80 | local jail="littlejet-server-wg"
81 | local node=
82 | local suffix="-wg"
83 |
84 | while getopts ":j:n:s:" _o; do
85 | case "${_o}" in
86 | j)
87 | jail="${OPTARG}"
88 | ;;
89 | n)
90 | node="${OPTARG}"
91 | ;;
92 | s)
93 | suffix="${OPTARG}"
94 | ;;
95 | *)
96 | usage
97 | ;;
98 | esac
99 | done
100 | shift $((OPTIND-1))
101 |
102 | local vpn
103 | vpn="$1"
104 |
105 | if [ -z "${vpn}" ]; then
106 | usage
107 | fi
108 |
109 | shift
110 |
111 | if ! checknodename "${vpn}"; then
112 | err "${vpn}: invalid node name."
113 | exit ${EX_NOINPUT}
114 | fi
115 |
116 | local errlevel
117 |
118 | local output
119 | output=`testnode "${vpn}" 2>&1`
120 |
121 | errlevel=$?
122 |
123 | if [ ${errlevel} -ne 0 ]; then
124 | err "Could not destroy VPN client due to an error on node '${vpn}': ${output}"
125 | exit ${EX_SOFTWARE}
126 | fi
127 |
128 | output=`remote_exc "${vpn}" "NO" "NO" appjail status -q -- "\"${jail}\"" 2>&1`
129 |
130 | errlevel=$?
131 |
132 | if [ ${errlevel} -eq 0 ]; then
133 | # pass
134 | elif [ ${errlevel} -eq 1 ]; then
135 | warn "VPN server on jail '${jail}', node '${vpn}', hasn't been started"
136 |
137 | remote_exc "${vpn}" "YES" "NO" appjail start -- "\"${jail}\""
138 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
139 | err "Could not destroy VPN client because the VPN server doesn't exist"
140 | exit ${EX_NOINPUT}
141 | else
142 | err "Could not check status of jail '${jail}' on node '${vpn}': ${output}"
143 | exit ${EX_SOFTWARE}
144 | fi
145 |
146 | output=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" test -f /.done 2>&1`
147 |
148 | errlevel=$?
149 |
150 | if [ ${errlevel} -eq 0 ]; then
151 | # pass
152 | elif [ ${errlevel} -eq 1 ]; then
153 | err "The VPN server on jail '${jail}' on node '${vpn}' does not appear to be healthy, make sure you deploy it correctly."
154 | exit ${EX_SOFTWARE}
155 | else
156 | err "Failed to check the status of jail '${jail}' on node '${vpn}': ${output}"
157 | exit ${EX_SOFTWARE}
158 | fi
159 |
160 | local allow_exclude=true
161 | local nodes
162 |
163 | if [ -n "${node}" ]; then
164 | if ! checknodename "${node}"; then
165 | err "${node}: invalid node name."
166 | exit ${EX_NOINPUT}
167 | fi
168 |
169 | if ! checknode "${node}"; then
170 | err "${node}: node cannot be found."
171 | exit ${EX_NOINPUT}
172 | fi
173 |
174 | nodes="${node}"
175 | allow_exclude=false
176 | else
177 | nodes=`jet get-nodes "${project}"` || exit $?
178 |
179 | if [ -z "${nodes}" ]; then
180 | exit ${EX_CANTCREAT}
181 | fi
182 | fi
183 |
184 | for node in ${nodes}; do
185 | if ${allow_exclude}; then
186 | local exclude
187 | exclude=`jet get-label "${project}" "${node}" vpn.wg.client.exclude 2> /dev/null`
188 |
189 | if [ -n "${exclude}" ]; then
190 | debug "Node '${node}' was excluded"
191 | continue
192 | fi
193 | fi
194 |
195 | local errlevel
196 |
197 | local output
198 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
199 |
200 | errlevel=$?
201 |
202 | if [ ${errlevel} -eq 0 ]; then
203 | # pass
204 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
205 | warn "${project}: project not found"
206 | continue
207 | else
208 | warn "Project '${project}' has an error: ${output}"
209 | continue
210 | fi
211 |
212 | local project_info
213 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe 2>&1`
214 |
215 | if [ $? -ne 0 ]; then
216 | warn "Error getting information about project '${project}' on node '${node}': ${project_info}"
217 | continue
218 | fi
219 |
220 | local state
221 | state=`echo -e "${project_info}" | safe_exc jq -r .state 2>&1`
222 |
223 | if [ $? -ne 0 ]; then
224 | warn "Error parsing information about project '${project}' on node '${node}': ${state}"
225 | continue
226 | fi
227 |
228 | debug "Project state is '${state}'"
229 |
230 | if ! [ "${state}" = "DONE" -o "${state}" = "DESTROYING" ]; then
231 | warn "State (${state}) is different than expected"
232 | continue
233 | fi
234 |
235 | local services_status
236 | services_status=`echo -e "${project_info}" | safe_exc jq -r '.services.[].status' 2>&1`
237 |
238 | if [ $? -ne 0 ]; then
239 | warn "Error parsing information about project '${project}' on node '${node}': ${services_status}"
240 | continue
241 | fi
242 |
243 | local service_index=0
244 |
245 | local service_status
246 | for service_status in ${services_status}; do
247 | local service_name
248 | service_name=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].name" 2>&1`
249 |
250 | if [ $? -ne 0 ]; then
251 | warn "Error parsing information about project '${project}' on node '${node}': ${service_name}"
252 | service_index=$((service_index+1))
253 | continue
254 | fi
255 |
256 | local peerid
257 | peerid="peer://${node}/${project}/${service_name}"
258 |
259 | remote_exc "${vpn}" "NO" "NO" \
260 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh check "${peerid}"
261 |
262 | errlevel=$?
263 |
264 | if [ ${errlevel} -eq ${EX_OK} ]; then
265 | warn "Destroying peer '${peerid}'"
266 |
267 | remote_exc "${vpn}" "NO" "NO" \
268 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh del "${peerid}"
269 |
270 | if [ $? -ne 0 ]; then
271 | warn "Error destroying peer '${peerid}'"
272 | fi
273 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
274 | # pass
275 | else
276 | warn "Error checking for existence of peer '${peerid}'"
277 | service_index=$((service_index+1))
278 | continue
279 | fi
280 |
281 | local vpn_jail
282 | vpn_jail="${project}-${service_name}${suffix}"
283 | vpn_jail=`printf "%s" "${vpn_jail}" | sed -Ee 's/\./_0X2E_/g'`
284 |
285 | output=`remote_exc "${node}" "NO" "NO" appjail status -q -- "\"${vpn_jail}\"" 2>&1`
286 |
287 | errlevel=$?
288 |
289 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
290 | if [ ${errlevel} -eq 0 ]; then
291 | warn "Stopping VPN jail '${vpn_jail}' on peer '${peerid}'"
292 |
293 | remote_exc "${node}" "NO" "NO" \
294 | appjail stop -- "\"${vpn_jail}\""
295 | fi
296 |
297 | warn "Destroying VPN jail '${vpn_jail}' on peer '${peerid}'"
298 |
299 | remote_exc "${node}" "NO" "NO" \
300 | appjail jail destroy -Rf -- "\"${vpn_jail}\""
301 |
302 | if [ $? -ne 0 ]; then
303 | warn "Error destroying VPN client on peer '${peerid}'"
304 | fi
305 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
306 | # pass
307 | else
308 | warn "Could not check status of jail '${jail}' on node '${vpn_jail}': ${output}"
309 | fi
310 |
311 | service_index=$((service_index+1))
312 | done
313 | done
314 |
315 | exit ${EX_OK}
316 | }
317 |
318 | usage()
319 | {
320 | err "usage: ${DEPLOY_NAME} [-j ] [-n ] [-s ] "
321 | exit ${EX_USAGE}
322 | }
323 |
324 | main "$@"
325 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.load-balancer.pen:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.load-balancer.pen"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | checkdependency jq
55 |
56 | local project
57 | project="${LITTLEJET_PROJECT}"
58 |
59 | if [ -z "${project}" ]; then
60 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
61 | exit ${EX_CONFIG}
62 | fi
63 |
64 | atexit_init
65 | atexit_add ". \"${lib_subr}\""
66 | atexit_add "setname \"${DEPLOY_NAME}\""
67 | atexit_add "load_config"
68 |
69 | local _o
70 | local opt_expose=false
71 | local opt_use_http_header=false
72 | local opt_use_hash=false
73 | local opt_use_roundrobin=false
74 | local opt_use_stubborn=false
75 | local opt_use_weight=false
76 | local opt_use_prio=false
77 | local blacklist=6
78 | local max_clients=2048
79 | local jail="littlejet-server-wg"
80 | local multi_accept=256
81 | local node=
82 | local port=1234
83 | local suffix="-lb"
84 | local tracked_seconds=6
85 | local timeout=10
86 | local backlog=500
87 | local virtual_network=
88 | local max_simultaneous_connections=500
89 |
90 | while getopts ":eHhrsWPb:c:J:m:n:p:S:T:t:q:v:x:" _o; do
91 | case "${_o}" in
92 | e)
93 | opt_expose=true
94 | ;;
95 | H)
96 | opt_use_http_header=true
97 | ;;
98 | h)
99 | opt_use_hash=true
100 | ;;
101 | r)
102 | opt_use_roundrobin=true
103 | ;;
104 | s)
105 | opt_use_stubborn=true
106 | ;;
107 | W)
108 | opt_use_weight=true
109 | ;;
110 | P)
111 | opt_use_prio=true
112 | ;;
113 | b)
114 | blacklist="${OPTARG}"
115 | ;;
116 | c)
117 | max_clients="${OPTARG}"
118 | ;;
119 | J)
120 | jail="${OPTARG}"
121 | ;;
122 | m)
123 | multi_accept="${OPTARG}"
124 | ;;
125 | n)
126 | node="${OPTARG}"
127 | ;;
128 | p)
129 | port="${OPTARG}"
130 | ;;
131 | S)
132 | suffix="${OPTARG}"
133 | ;;
134 | T)
135 | tracked_seconds="${OPTARG}"
136 | ;;
137 | t)
138 | timeout="${OPTARG}"
139 | ;;
140 | q)
141 | backlog="${OPTARG}"
142 | ;;
143 | v)
144 | virtual_network="${OPTARG}"
145 | ;;
146 | x)
147 | max_simultaneous_connections="${OPTARG}"
148 | ;;
149 | *)
150 | usage
151 | ;;
152 | esac
153 | done
154 | shift $((OPTIND-1))
155 |
156 | if [ $# -lt 2 ]; then
157 | usage
158 | fi
159 |
160 | local vpn
161 | vpn="$1"
162 |
163 | if ! checknodename "${vpn}"; then
164 | err "${vpn}: invalid node name."
165 | exit ${EX_NOINPUT}
166 | fi
167 |
168 | local target
169 | target="$2"
170 |
171 | if ! checknodename "${target}"; then
172 | err "${target}: invalid node name."
173 | exit ${EX_NOINPUT}
174 | fi
175 |
176 | shift 2
177 |
178 | local errlevel
179 |
180 | local output
181 | output=`testnode "${vpn}" 2>&1`
182 |
183 | errlevel=$?
184 |
185 | if [ ${errlevel} -ne 0 ]; then
186 | err "Could not deploy VPN load-balancer due to an error on node '${vpn}': ${output}"
187 | exit ${EX_SOFTWARE}
188 | fi
189 |
190 | output=`remote_exc "${vpn}" "NO" "NO" appjail status -q -- "\"${jail}\"" 2>&1`
191 |
192 | errlevel=$?
193 |
194 | if [ ${errlevel} -eq 0 ]; then
195 | # pass
196 | elif [ ${errlevel} -eq 1 ]; then
197 | warn "VPN server on jail '${jail}', node '${vpn}' hasn't been started"
198 |
199 | remote_exc "${vpn}" "YES" "NO" appjail start -- "\"${jail}\""
200 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
201 | err "VPN server doesn't exist, use 'vpn.wg.server' to deploy it."
202 | exit ${EX_NOINPUT}
203 | else
204 | err "Could not check status of jail '${jail}' on node '${vpn}': ${output}"
205 | exit ${EX_SOFTWARE}
206 | fi
207 |
208 | output=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" test -f /.done 2>&1`
209 |
210 | errlevel=$?
211 |
212 | if [ ${errlevel} -eq 0 ]; then
213 | # pass
214 | elif [ ${errlevel} -eq 1 ]; then
215 | err "The VPN server on jail '${jail}' on node '${vpn}' does not appear to be healthy, make sure you deploy it correctly."
216 | exit ${EX_SOFTWARE}
217 | else
218 | err "Failed to check the status of jail '${jail}' on node '${vpn}': ${output}"
219 | exit ${EX_SOFTWARE}
220 | fi
221 |
222 | local network_address
223 | network_address=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh get-network-addr 2>&1`
224 |
225 | if [ $? -ne 0 ]; then
226 | err "Failed to get network address on node '${vpn}': ${network_address}"
227 | exit ${EX_SOFTWARE}
228 | fi
229 |
230 | local allow_exclude=true
231 | local nodes
232 |
233 | if [ -n "${node}" ]; then
234 | if ! checknodename "${node}"; then
235 | err "${node}: invalid node name."
236 | exit ${EX_NOINPUT}
237 | fi
238 |
239 | if ! checknode "${node}"; then
240 | err "${node}: node cannot be found."
241 | exit ${EX_NOINPUT}
242 | fi
243 |
244 | nodes="${node}"
245 | allow_exclude=false
246 | else
247 | nodes=`jet get-nodes "${project}"` || exit $?
248 |
249 | if [ -z "${nodes}" ]; then
250 | exit ${EX_CANTCREAT}
251 | fi
252 | fi
253 |
254 | local conf
255 | conf=`tempdir` || exit $?
256 |
257 | atexit_add "removedir \"${conf}\""
258 |
259 | for node in ${nodes}; do
260 | if ${allow_exclude}; then
261 | local exclude
262 | exclude=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.exclude 2> /dev/null`
263 |
264 | if [ -n "${exclude}" ]; then
265 | debug "Node '${node}' was excluded"
266 | continue
267 | fi
268 | fi
269 |
270 | local penctl_extra_args=
271 |
272 | local node_max
273 | node_max=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.max 2> /dev/null`
274 |
275 | if [ -n "${node_max}" ]; then
276 | penctl_extra_args="max ${node_max}"
277 | fi
278 |
279 | local node_hard
280 | node_hard=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.hard 2> /dev/null`
281 |
282 | if [ -n "${node_hard}" ]; then
283 | penctl_extra_args="hard ${node_hard}"
284 | fi
285 |
286 | local node_weight
287 | node_weight=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.weight 2> /dev/null`
288 |
289 | if [ -n "${node_weight}" ]; then
290 | penctl_extra_args="weight ${node_weight}"
291 | fi
292 |
293 | local node_prio
294 | node_prio=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.prio 2> /dev/null`
295 |
296 | if [ -n "${node_prio}" ]; then
297 | penctl_extra_args="prio ${node_prio}"
298 | fi
299 |
300 | local errlevel
301 |
302 | local output
303 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
304 |
305 | errlevel=$?
306 |
307 | if [ ${errlevel} -eq 0 ]; then
308 | # pass
309 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
310 | warn "${project}: project not found"
311 | continue
312 | else
313 | warn "Project '${project}' has an error: ${output}"
314 | continue
315 | fi
316 |
317 | local project_info
318 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe 2>&1`
319 |
320 | if [ $? -ne 0 ]; then
321 | warn "Error getting information about project '${project}' on node '${node}': ${project_info}"
322 | continue
323 | fi
324 |
325 | local state
326 | state=`echo -e "${project_info}" | safe_exc jq -r .state 2>&1`
327 |
328 | if [ $? -ne 0 ]; then
329 | warn "Error parsing information about project '${project}' on node '${node}': ${state}"
330 | continue
331 | fi
332 |
333 | debug "Project state is '${state}'"
334 |
335 | if ! [ "${state}" = "DONE" -o "${state}" = "DESTROYING" ]; then
336 | warn "State (${state}) is different than expected"
337 | continue
338 | fi
339 |
340 | local services_status
341 | services_status=`echo -e "${project_info}" | safe_exc jq -r '.services.[].status' 2>&1`
342 |
343 | if [ $? -ne 0 ]; then
344 | warn "Error parsing information about project '${project}' on node '${node}': ${services_status}"
345 | continue
346 | fi
347 |
348 | local service_index=0
349 |
350 | local service_status
351 | for service_status in ${services_status}; do
352 | local service_name
353 | service_name=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].name" 2>&1`
354 |
355 | if [ $? -ne 0 ]; then
356 | warn "Error parsing information about project '${project}' on node '${node}': ${service_name}"
357 | service_index=$((service_index+1))
358 | continue
359 | fi
360 |
361 | if ! [ ${service_status} -eq 0 -o ${service_status} -eq 1 ]; then
362 | warn "Service '${service_name}' has a different status (${service_status}) than expected"
363 | service_index=$((service_index+1))
364 | continue
365 | fi
366 |
367 | local peerid
368 | peerid="peer://${node}/${project}/${service_name}"
369 |
370 | remote_exc "${vpn}" "NO" "NO" \
371 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh check "${peerid}"
372 |
373 | errlevel=$?
374 |
375 | if [ ${errlevel} -eq ${EX_OK} ]; then
376 | # pass
377 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
378 | warn "Peer '${peerid}' cannot be found"
379 | service_index=$((service_index+1))
380 | continue
381 | else
382 | warn "Error checking for existence of peer '${peerid}'"
383 | service_index=$((service_index+1))
384 | continue
385 | fi
386 |
387 | local peer_address
388 | peer_address=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh get-addr "${peerid}" 2>&1`
389 |
390 | if [ $? -ne 0 ]; then
391 | warn "Error getting information about peer '${peerid}': ${peer_address}"
392 | service_index=$((service_index+1))
393 | continue
394 | fi
395 |
396 | local peerid_lb
397 | peerid_lb="peer://${target}/${project}/${service_name}/load-balancer"
398 |
399 | remote_exc "${vpn}" "NO" "NO" \
400 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh check "${peerid_lb}"
401 |
402 | errlevel=$?
403 |
404 | if [ ${errlevel} -eq ${EX_OK} ]; then
405 | # pass
406 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
407 | remote_exc "${vpn}" "NO" "NO" \
408 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh add "${peerid_lb}"
409 |
410 | if [ $? -ne 0 ]; then
411 | warn "Error creating peer '${peerid_lb}'"
412 | service_index=$((service_index+1))
413 | continue
414 | fi
415 | else
416 | warn "Error checking for existence of peer '${peerid_lb}'"
417 | service_index=$((service_index+1))
418 | continue
419 | fi
420 |
421 | local service_jail
422 | service_jail=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].jail" 2>&1`
423 |
424 | if [ $? -ne 0 ]; then
425 | warn "Error parsing information about project '${project}' on node '${node}': ${service_jail}"
426 | service_index=$((service_index+1))
427 | continue
428 | fi
429 |
430 | local labels
431 | labels="vpn.wg.client.from.port vpn.wg.client.proto"
432 | labels="${labels} vpn.wg.load-balancer.pen.include.me"
433 |
434 | local label_from_port=
435 | local label_proto="tcp"
436 | local label_include_me=
437 |
438 | local label
439 | for label in ${labels}; do
440 | local value
441 | value=`remote_exc "${node}" "NO" "NO" appjail label get -I -l "${label}" -- "${service_jail}" value 2>&1`
442 |
443 | errlevel=$?
444 |
445 | if [ ${errlevel} -eq 0 ]; then
446 | # pass
447 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
448 | continue
449 | else
450 | warn "Error getting label '${label}' from service '${service_name}': ${value}"
451 | continue
452 | fi
453 |
454 | case "${label}" in
455 | vpn.wg.client.from.port)
456 | setvar "label_from_port" "${value}"
457 | ;;
458 | vpn.wg.client.proto)
459 | setvar "label_proto" "${value}"
460 | ;;
461 | vpn.wg.load-balancer.pen.include.me)
462 | setvar "label_include_me" "${value}"
463 | ;;
464 | esac
465 | done
466 |
467 | if [ -z "${label_include_me}" ]; then
468 | debug "Service '${service_name}' doesn't have the label 'vpn.wg.load-balancer.pen.include.me'"
469 | service_index=$((service_index+1))
470 | continue
471 | fi
472 |
473 | if [ -z "${label_from_port}" ]; then
474 | warn "Service '${service_name}' wants to be included in the list but has not defined an external port"
475 | service_index=$((service_index+1))
476 | continue
477 | fi
478 |
479 | if ! checknumber "${label_from_port}" || ! checkport "${label_from_port}"; then
480 | want "Service '${service_name}' has an invalid external port: ${label_from_port}"
481 | service_index=$((service_index+1))
482 | continue
483 | fi
484 |
485 | case "${label_proto}" in
486 | tcp|udp)
487 | ;;
488 | *)
489 | warn "Service '${service_name}' has an invalid protocol: ${label_proto}"
490 | service_index=$((service_index+1))
491 | continue
492 | ;;
493 | esac
494 |
495 | local lb_jail
496 | lb_jail="${project}-${service_name}${suffix}"
497 | lb_jail=`printf "%s" "${lb_jail}" | sed -Ee 's/\./_0X2E_/g'`
498 |
499 | local ident
500 | ident="${project}-${service_name}"
501 |
502 | if [ $? -ne 0 ]; then
503 | service_index=$((service_index+1))
504 | continue
505 | fi
506 |
507 | local identdir
508 | identdir="${conf}/${ident}"
509 |
510 | if [ ! -d "${identdir}" ]; then
511 | if ! mkdir -p -- "${identdir}"; then
512 | service_index=$((service_index+1))
513 | continue
514 | fi
515 | fi
516 |
517 | local addr4penctl
518 | addr4penctl="address ${peer_address} port ${label_from_port}"
519 |
520 | if [ -n "${penctl_extra_args}" ]; then
521 | addr4penctl="${addr4penctl} ${penctl_extra_args}"
522 | fi
523 |
524 | if ! printf "%s\n" "${addr4penctl}" >> "${identdir}/services"; then
525 | if [ ! -f "${identdir}/skip" ]; then
526 | touch -- "${identdir}/skip"
527 | fi
528 | service_index=$((service_index+1))
529 | continue
530 | fi
531 |
532 | if [ ! -f "${identdir}/proto" ]; then
533 | if ! printf "%s" "${label_proto}" > "${identdir}/proto"; then
534 | if [ ! -f "${identdir}/skip" ]; then
535 | touch -- "${identdir}/skip"
536 | fi
537 | service_index=$((service_index+1))
538 | continue
539 | fi
540 | fi
541 |
542 | if [ ! -f "${identdir}/service_name" ]; then
543 | if ! printf "%s" "${service_name}" > "${identdir}/service_name"; then
544 | if [ ! -f "${identdir}/skip" ]; then
545 | touch -- "${identdir}/skip"
546 | fi
547 | service_index=$((service_index+1))
548 | continue
549 | fi
550 | fi
551 |
552 | if [ ! -f "${identdir}/jail" ]; then
553 | if ! printf "%s" "${lb_jail}" > "${identdir}/jail"; then
554 | if [ ! -f "${identdir}/skip" ]; then
555 | touch -- "${identdir}/skip"
556 | fi
557 | service_index=$((service_index+1))
558 | continue
559 | fi
560 | fi
561 |
562 | service_index=$((service_index+1))
563 | done
564 | done
565 |
566 | local ident
567 | for ident in `ls -1 -- "${conf}"`; do
568 | local identdir
569 | identdir="${conf}/${ident}"
570 |
571 | if [ -f "${identdir}/skip" ]; then
572 | continue
573 | fi
574 |
575 | if [ ! -f "${identdir}/services" ]; then
576 | continue
577 | fi
578 |
579 | if [ ! -f "${identdir}/proto" ]; then
580 | continue
581 | fi
582 |
583 | local proto
584 | proto=`head -1 -- "${identdir}/proto"` || continue
585 |
586 | if [ -z "${proto}" ]; then
587 | continue
588 | fi
589 |
590 | if [ ! -f "${identdir}/jail" ]; then
591 | continue
592 | fi
593 |
594 | local lb_jail
595 | lb_jail=`head -1 -- "${identdir}/jail"` || continue
596 |
597 | if [ -z "${lb_jail}" ]; then
598 | continue
599 | fi
600 |
601 | if [ ! -f "${identdir}/service_name" ]; then
602 | continue
603 | fi
604 |
605 | local service_name
606 | service_name=`head -1 -- "${identdir}/service_name"` || continue
607 |
608 | if [ -z "${service_name}" ]; then
609 | continue
610 | fi
611 |
612 | output=`remote_exc "${target}" "NO" "NO" appjail status -q -- "\"${lb_jail}\"" 2>&1`
613 |
614 | errlevel=$?
615 |
616 | local deploy_lb=false
617 |
618 | if [ ${errlevel} -eq 0 ]; then
619 | # pass
620 | elif [ ${errlevel} -eq 1 ]; then
621 | warn "Load-balancer on jail '${lb_jail}', node '${target}', hasn't been started"
622 |
623 | remote_exc "${target}" "NO" "NO" appjail start -- "\"${lb_jail}\""
624 |
625 | if [ $? -ne 0 ]; then
626 | warn "Error starting jail '${lb_jail}'"
627 | continue
628 | fi
629 |
630 | output=`remote_exc "${target}" "NO" "NO" appjail cmd jexec "\"${lb_jail}\"" test -f /.done 2>&1`
631 |
632 | errlevel=$?
633 |
634 | if [ ${errlevel} -eq 0 ]; then
635 | # pass
636 | elif [ ${errlevel} -eq 1 ]; then
637 | warn "Load-balancer on jail '${lb_jail}' on node '${taret}' does not appear to be healthy, re-deploying"
638 |
639 | deploy_lb=true
640 | else
641 | warn "Failed to check the status of jail '${lb_jail}' on node '${target}': ${output}"
642 | continue
643 | fi
644 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
645 | info "Creating load-balancer on jail '${lb_jail}', node '${target}'"
646 |
647 | deploy_lb=true
648 | else
649 | warn "Could not check status of jail '${lb_jail}' on node '${target}': ${output}"
650 | continue
651 | fi
652 |
653 | local peerid
654 | peerid="peer://${target}/${project}/${service_name}/load-balancer"
655 |
656 | if ${deploy_lb}; then
657 | local local_wg_conf
658 | local_wg_conf=`tempfile`
659 |
660 | if [ $? -ne 0 ]; then
661 | warn "Error creating a temporary file locally: ${local_wg_conf}"
662 | continue
663 | fi
664 |
665 | atexit_add "removefile \"${local_wg_conf}\""
666 |
667 | local wg_conf
668 | wg_conf=`remote_exc "${target}" "NO" "NO" mktemp -t littlejet-vpn.wg.client 2>&1`
669 |
670 | if [ $? -ne 0 ]; then
671 | warn "Error while remotely creating a temporary file: ${wg_conf}"
672 | continue
673 | fi
674 |
675 | remote_exc "${vpn}" "NO" "NO" \
676 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh show "${peerid}" > "${local_wg_conf}"
677 |
678 | if [ $? -ne 0 ]; then
679 | warn "Error writing locally the WireGuard configuration file of peer '${peerid}'"
680 | remote_exc "${target}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
681 | continue
682 | fi
683 |
684 | (mirror "${local_wg_conf}" "${target}:${wg_conf}")
685 |
686 | if [ $? -ne 0 ]; then
687 | warn "Error uploading the WireGuard configuration file to peer '${peerid}'"
688 | remote_exc "${target}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
689 | continue
690 | fi
691 |
692 | removefile "${local_wg_conf}"
693 |
694 | set --
695 |
696 | if ${opt_expose}; then
697 | set -- -o "\"expose=${port} proto:${proto}\""
698 | fi
699 |
700 | local services
701 | services=`cat -- "${identdir}/services" | tr '\n' ';' | sed -Ee 's/;$//'`
702 |
703 | set -- "$@" --
704 | set -- "$@" --wg_pen_services "\"${services}\""
705 | set -- "$@" --wg_pen_port "${port}"
706 | set -- "$@" --wg_pen_blacklist "${blacklist}"
707 | set -- "$@" --wg_pen_tracked_seconds "${tracked_seconds}"
708 | set -- "$@" --wg_pen_max_clients "${max_clients}"
709 | set -- "$@" --wg_pen_backlog "${backlog}"
710 | set -- "$@" --wg_pen_timeout "${timeout}"
711 | set -- "$@" --wg_pen_max_simultaneous_connections "${max_simultaneous_connections}"
712 | set -- "$@" --wg_pen_multi_accept "${multi_accept}"
713 | set -- "$@" --wg_conf "\"${wg_conf}\""
714 |
715 | if ${opt_expose}; then
716 | port=$((port+1))
717 |
718 | if ! checkport "${port}"; then
719 | warn "${port}: invalid port."
720 | remote_exc "${target}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
721 | continue
722 | fi
723 | fi
724 |
725 | if [ "${proto}" = "udp" ]; then
726 | set -- "$@" --wg_pen_use_udp 1
727 | fi
728 |
729 | if ${opt_use_http_header}; then
730 | set -- "$@" --wg_pen_use_http_header 1
731 | fi
732 |
733 | if ${opt_use_hash}; then
734 | set -- "$@" --wg_pen_use_hash 1
735 | fi
736 |
737 | if ${opt_use_roundrobin}; then
738 | set -- "$@" --wg_pen_use_roundrobin 1
739 | fi
740 |
741 | if ${opt_use_stubborn}; then
742 | set -- "$@" --wg_pen_use_stubborn 1
743 | fi
744 |
745 | if ${opt_use_weight}; then
746 | set -- "$@" --wg_pen_use_weight 1
747 | fi
748 |
749 | if ${opt_use_prio}; then
750 | set -- "$@" --wg_pen_use_prio 1
751 | fi
752 |
753 | if [ -n "${virtual_network}" ]; then
754 | set -- "$@" --wg_virtualnet "${virtual_network}"
755 | fi
756 |
757 | remote_exc "${target}" "NO" "NO" \
758 | appjail makejail \
759 | -j "\"${lb_jail}\"" \
760 | -f "\"gh+DtxdF/LittleJet-wg-makejail --file load-balancer.makejail\"" \
761 | "$@"
762 |
763 | if [ $? -ne 0 ]; then
764 | warn "Error deploying load-balancer on node '${target}'"
765 | remote_exc "${target}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
766 | continue
767 | fi
768 |
769 | remote_exc "${target}" "NO" "NO" rm -f -- "\"${wg_conf}\"" 2> /dev/null
770 | else
771 | info "Updating load-balancer information"
772 |
773 | set --
774 |
775 | local service
776 | while IFS= read -r service; do
777 | set -- "$@" "\"${service}\""
778 | done < "${identdir}/services"
779 |
780 | remote_exc "${target}" "NO" "NO" \
781 | appjail cmd jexec "\"${lb_jail}\"" /scripts/update-servers.sh "$@"
782 |
783 | if [ $? -ne 0 ]; then
784 | warn "Error updating load-balancer information"
785 | fi
786 | fi
787 | done
788 |
789 | removedir "${conf}"
790 |
791 | exit ${EX_OK}
792 | }
793 |
794 | usage()
795 | {
796 | err "usage: ${DEPLOY_NAME} [-eHhrsWP] [-b ] [-c ] [-J ] [-m ]"
797 | err " [-n ] [-p ] [-S ] [-T ] [-t ]"
798 | err " [-q ] [-v ] [-x ] "
799 | exit ${EX_USAGE}
800 | }
801 |
802 | main "$@"
803 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.load-balancer.pen.destroy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.load-balancer.pen.destroy"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | checkdependency jq
55 |
56 | local project
57 | project="${LITTLEJET_PROJECT}"
58 |
59 | if [ -z "${project}" ]; then
60 | err "LITTLEJET_PROJECT: environment variable hasn't been defined."
61 | exit ${EX_CONFIG}
62 | fi
63 |
64 | atexit_init
65 | atexit_add ". \"${lib_subr}\""
66 | atexit_add "setname \"${DEPLOY_NAME}\""
67 | atexit_add "load_config"
68 |
69 | local _o
70 | local jail="littlejet-server-wg"
71 | local node=
72 | local suffix="-lb"
73 |
74 | while getopts ":J:n:S:" _o; do
75 | case "${_o}" in
76 | J)
77 | jail="${OPTARG}"
78 | ;;
79 | n)
80 | node="${OPTARG}"
81 | ;;
82 | S)
83 | suffix="${OPTARG}"
84 | ;;
85 | *)
86 | usage
87 | ;;
88 | esac
89 | done
90 | shift $((OPTIND-1))
91 |
92 | if [ $# -lt 2 ]; then
93 | usage
94 | fi
95 |
96 | local vpn
97 | vpn="$1"
98 |
99 | if ! checknodename "${vpn}"; then
100 | err "${vpn}: invalid node name."
101 | exit ${EX_NOINPUT}
102 | fi
103 |
104 | local target
105 | target="$2"
106 |
107 | if ! checknodename "${target}"; then
108 | err "${target}: invalid node name."
109 | exit ${EX_NOINPUT}
110 | fi
111 |
112 | shift 2
113 |
114 | local errlevel
115 |
116 | local output
117 | output=`testnode "${vpn}" 2>&1`
118 |
119 | errlevel=$?
120 |
121 | if [ ${errlevel} -ne 0 ]; then
122 | err "Could not destroy VPN load-balancer due to an error on node '${vpn}': ${output}"
123 | exit ${EX_SOFTWARE}
124 | fi
125 |
126 | output=`remote_exc "${vpn}" "NO" "NO" appjail status -q -- "\"${jail}\"" 2>&1`
127 |
128 | errlevel=$?
129 |
130 | if [ ${errlevel} -eq 0 ]; then
131 | # pass
132 | elif [ ${errlevel} -eq 1 ]; then
133 | warn "VPN server on jail '${jail}', node '${vpn}' hasn't been started"
134 |
135 | remote_exc "${vpn}" "YES" "NO" appjail start -- "\"${jail}\""
136 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
137 | err "Could not destroy VPN load-balancer because the VPN server doesn't exist"
138 | exit ${EX_NOINPUT}
139 | else
140 | err "Could not check status of jail '${jail}' on node '${vpn}': ${output}"
141 | exit ${EX_SOFTWARE}
142 | fi
143 |
144 | output=`remote_exc "${vpn}" "NO" "NO" appjail cmd jexec "\"${jail}\"" test -f /.done 2>&1`
145 |
146 | errlevel=$?
147 |
148 | if [ ${errlevel} -eq 0 ]; then
149 | # pass
150 | elif [ ${errlevel} -eq 1 ]; then
151 | err "The VPN server on jail '${jail}' on node '${vpn}' does not appear to be healthy, make sure you deploy it correctly."
152 | exit ${EX_SOFTWARE}
153 | else
154 | err "Failed to check the status of jail '${jail}' on node '${vpn}': ${output}"
155 | exit ${EX_SOFTWARE}
156 | fi
157 |
158 | local allow_exclude=true
159 | local nodes
160 |
161 | if [ -n "${node}" ]; then
162 | if ! checknodename "${node}"; then
163 | err "${node}: invalid node name."
164 | exit ${EX_NOINPUT}
165 | fi
166 |
167 | if ! checknode "${node}"; then
168 | err "${node}: node cannot be found."
169 | exit ${EX_NOINPUT}
170 | fi
171 |
172 | nodes="${node}"
173 | allow_exclude=false
174 | else
175 | nodes=`jet get-nodes "${project}"` || exit $?
176 |
177 | if [ -z "${nodes}" ]; then
178 | exit ${EX_CANTCREAT}
179 | fi
180 | fi
181 |
182 | local services_lst
183 | services_lst=`tempfile` || exit $?
184 |
185 | atexit_add "removefile \"${services_lst}\""
186 |
187 | for node in ${nodes}; do
188 | if ${allow_exclude}; then
189 | local exclude
190 | exclude=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.exclude 2> /dev/null`
191 |
192 | if [ -n "${exclude}" ]; then
193 | debug "Node '${node}' was excluded"
194 | continue
195 | fi
196 | fi
197 |
198 | local penctl_extra_args=
199 |
200 | local node_max
201 | node_max=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.max 2> /dev/null`
202 |
203 | if [ -n "${node_max}" ]; then
204 | penctl_extra_args="max ${node_max}"
205 | fi
206 |
207 | local node_hard
208 | node_hard=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.hard 2> /dev/null`
209 |
210 | if [ -n "${node_hard}" ]; then
211 | penctl_extra_args="hard ${node_hard}"
212 | fi
213 |
214 | local node_weight
215 | node_weight=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.weight 2> /dev/null`
216 |
217 | if [ -n "${node_weight}" ]; then
218 | penctl_extra_args="weight ${node_weight}"
219 | fi
220 |
221 | local node_prio
222 | node_prio=`jet get-label "${project}" "${node}" vpn.wg.load-balancer.pen.prio 2> /dev/null`
223 |
224 | if [ -n "${node_prio}" ]; then
225 | penctl_extra_args="prio ${node_prio}"
226 | fi
227 |
228 | local errlevel
229 |
230 | local output
231 | output=`run_director "${project}" "${node}" "NO" "NO" check 2>&1`
232 |
233 | errlevel=$?
234 |
235 | if [ ${errlevel} -eq 0 ]; then
236 | # pass
237 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
238 | warn "${project}: project not found"
239 | continue
240 | else
241 | warn "Project '${project}' has an error: ${output}"
242 | continue
243 | fi
244 |
245 | local project_info
246 | project_info=`run_director "${project}" "${node}" "YES" "NO" describe 2>&1`
247 |
248 | if [ $? -ne 0 ]; then
249 | warn "Error getting information about project '${project}' on node '${node}': ${project_info}"
250 | continue
251 | fi
252 |
253 | local state
254 | state=`echo -e "${project_info}" | safe_exc jq -r .state 2>&1`
255 |
256 | if [ $? -ne 0 ]; then
257 | warn "Error parsing information about project '${project}' on node '${node}': ${state}"
258 | continue
259 | fi
260 |
261 | debug "Project state is '${state}'"
262 |
263 | if ! [ "${state}" = "DONE" -o "${state}" = "DESTROYING" ]; then
264 | warn "State (${state}) is different than expected"
265 | continue
266 | fi
267 |
268 | local services_status
269 | services_status=`echo -e "${project_info}" | safe_exc jq -r '.services.[].status' 2>&1`
270 |
271 | if [ $? -ne 0 ]; then
272 | warn "Error parsing information about project '${project}' on node '${node}': ${services_status}"
273 | continue
274 | fi
275 |
276 | local service_index=0
277 |
278 | local service_status
279 | for service_status in ${services_status}; do
280 | local service_name
281 | service_name=`echo -e "${project_info}" | safe_exc jq -r ".services.[${service_index}].name" 2>&1`
282 |
283 | if [ $? -ne 0 ]; then
284 | warn "Error parsing information about project '${project}' on node '${node}': ${service_name}"
285 | service_index=$((service_index+1))
286 | continue
287 | fi
288 |
289 | local peerid_lb
290 | peerid_lb="peer://${target}/${project}/${service_name}/load-balancer"
291 |
292 | remote_exc "${vpn}" "NO" "NO" \
293 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh check "${peerid_lb}"
294 |
295 | errlevel=$?
296 |
297 | if [ ${errlevel} -eq ${EX_OK} ]; then
298 | warn "Destroying peer '${peerid_lb}'"
299 |
300 | remote_exc "${vpn}" "NO" "NO" \
301 | appjail cmd jexec "\"${jail}\"" /scripts/run-with-lock.sh del "${peerid_lb}"
302 |
303 | if [ $? -ne 0 ]; then
304 | warn "Error destroying peer '${peerid_lb}'"
305 | fi
306 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
307 | # pass
308 | else
309 | warn "Error checking for existence of peer '${peerid_lb}'"
310 | service_index=$((service_index+1))
311 | continue
312 | fi
313 |
314 | local lb_jail
315 | lb_jail="${project}-${service_name}${suffix}"
316 | lb_jail=`printf "%s" "${lb_jail}" | sed -Ee 's/\./_0X2E_/g'`
317 |
318 | printf "%s\n" "${lb_jail}" >> "${services_lst}"
319 |
320 | service_index=$((service_index+1))
321 | done
322 | done
323 |
324 | local service_jail
325 | for service_jail in `cat -- "${services_lst}" | sort | uniq`; do
326 | output=`remote_exc "${target}" "NO" "NO" appjail status -q -- "\"${service_jail}\"" 2>&1`
327 |
328 | errlevel=$?
329 |
330 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
331 | if [ ${errlevel} -eq 0 ]; then
332 | warn "Stopping load-balancer jail '${service_jail}' on node '${target}'"
333 |
334 | remote_exc "${target}" "NO" "NO" \
335 | appjail stop -- "\"${service_jail}\""
336 | fi
337 |
338 | warn "Destroying VPN load-balancer '${service_jail}' on node '${target}'"
339 |
340 | remote_exc "${target}" "NO" "NO" \
341 | appjail jail destroy -Rf -- "\"${service_jail}\""
342 |
343 | if [ $? -ne 0 ]; then
344 | warn "Error destroying VPN load-balancer on node '${target}'"
345 | fi
346 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
347 | # pass
348 | else
349 | warn "Could not check status of jail '${jail}' on node '${service_jail}': ${output}"
350 | fi
351 | done
352 |
353 | removefile "${services_lst}"
354 |
355 | exit ${EX_OK}
356 | }
357 |
358 | usage()
359 | {
360 | err "usage: ${DEPLOY_NAME} [-J ] [-n node] [-S ] "
361 | exit ${EX_USAGE}
362 | }
363 |
364 | main "$@"
365 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.server:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.server"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local _o
55 | local opt_expose=true
56 | local jail_name="littlejet-server-wg"
57 | local port=51820
58 | local network=
59 | local mtu=
60 | local persistentkeepalive=
61 | local virtual_network=
62 | local endpoint=
63 |
64 | while getopts ":Ej:p:n:m:P:v:e:" _o; do
65 | case "${_o}" in
66 | E)
67 | opt_expose=false
68 | ;;
69 | j)
70 | jail_name="${OPTARG}"
71 | ;;
72 | p)
73 | port="${OPTARG}"
74 | ;;
75 | n)
76 | network="${OPTARG}"
77 | ;;
78 | m)
79 | mtu="${OPTARG}"
80 | ;;
81 | P)
82 | persistentkeepalive="${OPTARG}"
83 | ;;
84 | v)
85 | virtual_network="${OPTARG}"
86 | ;;
87 | e)
88 | endpoint="${OPTARG}"
89 | ;;
90 | *)
91 | usage
92 | ;;
93 | esac
94 | done
95 | shift $((OPTIND-1))
96 |
97 | if [ -z "${endpoint}" ]; then
98 | usage
99 | fi
100 |
101 | local node
102 | node="$1"
103 |
104 | if [ -z "${node}" ]; then
105 | usage
106 | fi
107 |
108 | shift
109 |
110 | set --
111 |
112 | if ! checknumber "${port}"; then
113 | err "${port}: port must be a number."
114 | exit ${EX_DATAERR}
115 | fi
116 |
117 | if ! checkport "${port}"; then
118 | err "${port}: invalid port."
119 | exit ${EX_DATAERR}
120 | fi
121 |
122 | if ${opt_expose}; then
123 | set -- -o "\"expose=${port} proto:udp\""
124 | fi
125 |
126 | set -- "$@" -V WG_PORT="${port}"
127 |
128 | if [ -n "${mtu}" ]; then
129 | if ! checknumber "${mtu}"; then
130 | err "${mtu}: MTU must be a number."
131 | exit ${EX_DATAERR}
132 | fi
133 |
134 | set -- "$@" -V WG_MTU="${mtu}"
135 | fi
136 |
137 | if [ -n "${persistentkeepalive}" ]; then
138 | if ! checknumber "${persistentkeepalive}"; then
139 | err "${persistentkeepalive}: PersistentKeepalive must be a number."
140 | exit ${EX_DATAERR}
141 | fi
142 |
143 | set -- "$@" -V WG_PERSISTENTKEEPALIVE="${persistentkeepalive}"
144 | fi
145 |
146 | if [ -n "${network}" ]; then
147 | set -- "$@" -V "\"WG_NETWORK=${network}\""
148 | fi
149 |
150 | if [ -n "${virtual_network}" ]; then
151 | set -- "$@" -- --wg_virtualnet "\"${virtual_network}\""
152 | fi
153 |
154 | if ! checknodename "${node}"; then
155 | err "${node}: invalid node name."
156 | exit ${EX_DATAERR}
157 | fi
158 |
159 | local errlevel
160 |
161 | local output
162 | output=`testnode "${node}" 2>&1`
163 |
164 | errlevel=$?
165 |
166 | if [ ${errlevel} -ne 0 ]; then
167 | err "Could not deploy VPN server on node '${node}': ${output}"
168 | exit ${EX_NOPERM}
169 | fi
170 |
171 | info "Deploying VPN server on node '${node}'"
172 |
173 | remote_exc "${node}" "YES" "NO" \
174 | appjail makejail \
175 | -j "\"${jail_name}\"" \
176 | -f "\"gh+DtxdF/LittleJet-wg-makejail --file server.makejail\"" \
177 | -V WG_ENDPOINT="${endpoint}:${port}" \
178 | "$@"
179 |
180 | remote_exc "${node}" "YES" "NO" \
181 | appjail start -- "\"${jail_name}\""
182 |
183 | exit ${EX_OK}
184 | }
185 |
186 | usage()
187 | {
188 | err "usage: ${DEPLOY_NAME} [-E] [-j ] [-p ] [-n ] [-m ]"
189 | err " [-P ] [-v ] -e "
190 | exit ${EX_USAGE}
191 | }
192 |
193 | main "$@"
194 |
--------------------------------------------------------------------------------
/share/littlejet/runscripts/vpn.wg.server.destroy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | #
9 | # * Redistributions of source code must retain the above copyright notice, this
10 | # list of conditions and the following disclaimer.
11 | #
12 | # * Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # * Neither the name of the copyright holder nor the names of its
17 | # contributors may be used to endorse or promote products derived from
18 | # this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | DEPLOY_NAME="vpn.wg.server.destroy"
32 |
33 | main()
34 | {
35 | local lib_subr
36 | lib_subr="${LITTLEJET_LIB_SUBR}"
37 |
38 | if [ -z "${lib_subr}" ]; then
39 | echo "LITTLEJET_LIB_SUBR: environment variable hasn't been defined." >&2
40 | exit 78 # EX_CONFIG
41 | fi
42 |
43 | if [ ! -f "${lib_subr}" ]; then
44 | echo "${lib_subr}: library cannot be found." >&2
45 | exit 66 # EX_NOINPUT
46 | fi
47 |
48 | . "${lib_subr}"
49 |
50 | load_config
51 |
52 | setname "${DEPLOY_NAME}"
53 |
54 | local _o
55 | local jail_name="littlejet-server-wg"
56 |
57 | while getopts ":j:" _o; do
58 | case "${_o}" in
59 | j)
60 | jail_name="${OPTARG}"
61 | ;;
62 | *)
63 | usage
64 | ;;
65 | esac
66 | done
67 | shift $((OPTIND-1))
68 |
69 | local node
70 | node="$1"
71 |
72 | if [ -z "${node}" ]; then
73 | usage
74 | fi
75 |
76 | if ! checknodename "${node}"; then
77 | err "${node}: invalid node name."
78 | exit ${EX_DATAERR}
79 | fi
80 |
81 | local errlevel
82 |
83 | local output
84 | output=`testnode "${node}" 2>&1`
85 |
86 | errlevel=$?
87 |
88 | if [ ${errlevel} -ne 0 ]; then
89 | err "Could not destroy VPN server on node '${node}': ${output}"
90 | exit ${EX_NOPERM}
91 | fi
92 |
93 | output=`remote_exc "${node}" "NO" "NO" appjail status -q -- "\"${jail_name}\"" 2>&1`
94 |
95 | errlevel=$?
96 |
97 | if [ ${errlevel} -eq 0 -o ${errlevel} -eq 1 ]; then
98 | warn "Destroying VPN server on node '${node}'"
99 |
100 | remote_exc "${node}" "NO" "YES" \
101 | appjail stop -- "\"${jail_name}\""
102 |
103 | errlevel=$?
104 |
105 | if [ ${errlevel} -ne 0 ]; then
106 | err "Error stopping VPN jail '${jail_name}' on node '${node}'"
107 | exit ${errlevel}
108 | fi
109 |
110 | remote_exc "${node}" "NO" "YES" \
111 | appjail jail destroy -Rf -- "\"${jail_name}\""
112 |
113 | errlevel=$?
114 |
115 | if [ ${errlevel} -ne 0 ]; then
116 | err "Error destroying VPN jail '${jail_name}' on node '${node}'"
117 | exit ${errlevel}
118 | fi
119 | elif [ ${errlevel} -eq ${EX_NOINPUT} ]; then
120 | err "Cannot find VPN jail '${jail_name}' on node '${node}'"
121 | exit ${EX_NOINPUT}
122 | else
123 | err "Could not check status of jail '${jail_name}' on node '${jail_name}': ${output}"
124 | exit ${errlevel}
125 | fi
126 |
127 | exit ${EX_OK}
128 | }
129 |
130 | usage()
131 | {
132 | err "usage: ${DEPLOY_NAME} [-j ] "
133 | exit ${EX_USAGE}
134 | }
135 |
136 | main "$@"
137 |
--------------------------------------------------------------------------------
/share/man/man1/littlejet.1:
--------------------------------------------------------------------------------
1 | .\"Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
2 | .\"All rights reserved.
3 | .\"
4 | .\"Redistribution and use in source and binary forms, with or without
5 | .\"modification, are permitted provided that the following conditions are met:
6 | .\"
7 | .\"* Redistributions of source code must retain the above copyright notice, this
8 | .\" list of conditions and the following disclaimer.
9 | .\"
10 | .\"* Redistributions in binary form must reproduce the above copyright notice,
11 | .\" this list of conditions and the following disclaimer in the documentation
12 | .\" and/or other materials provided with the distribution.
13 | .\"
14 | .\"* Neither the name of the copyright holder nor the names of its
15 | .\" contributors may be used to endorse or promote products derived from
16 | .\" this software without specific prior written permission.
17 | .\"
18 | .\"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | .\"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | .\"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | .\"DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | .\"FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | .\"DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | .\"SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | .\"CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | .\"OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | .\"OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | .Dd July 1, 2024
29 | .Dt LITTLEJET 1
30 | .Os
31 | .Sh NAME
32 | .Nm littlejet
33 | .Nd Create, deploy, manage and scale FreeBSD jails anywhere
34 | .Sh SYNOPSIS
35 | .Nm
36 | .Cm usage
37 | .Nm
38 | .Cm version
39 | .Pp
40 | .Nm
41 | .Cm add-label
42 | .Ar project
43 | .Ar node
44 | .Ar label Ns = Ns Ar value No "..."
45 | .Nm
46 | .Cm del-label
47 | .Ar project
48 | .Ar node
49 | .Ar label
50 | .Nm
51 | .Cm del-labels
52 | .Ar project
53 | .Ar node
54 | .Nm
55 | .Cm get-label
56 | .Ar project
57 | .Ar node
58 | .Ar label
59 | .Nm
60 | .Cm get-labels
61 | .Ar project
62 | .Ar node
63 | .Pp
64 | .Nm
65 | .Cm add-node
66 | .Op Fl T
67 | .Ar project
68 | .Ar node
69 | .Nm
70 | .Cm copy-nodes
71 | .Op Fl O
72 | .Ar src-project
73 | .Ar dst-project
74 | .Op Ar node
75 | .Nm
76 | .Cm del-node
77 | .Ar project
78 | .Ar node
79 | .Nm
80 | .Cm del-nodes
81 | .Ar project
82 | .Pp
83 | .Nm
84 | .Cm copy
85 | .Op Fl fN
86 | .Ar project
87 | .Ar new-project
88 | .Nm
89 | .Cm create
90 | .Op Fl f
91 | .Ar project
92 | .Op Ar director-file
93 | .Nm
94 | .Cm destroy
95 | .Ar project
96 | .Nm
97 | .Cm get-nodes
98 | .Ar project
99 | .Nm
100 | .Cm get-projects
101 | .Nm
102 | .Cm rename
103 | .Ar project
104 | .Ar new-project
105 | .Nm
106 | .Cm show
107 | .Op Ar project
108 | .Pp
109 | .Nm
110 | .Cm run-appjail
111 | .Op Fl CHPs
112 | .Op Fl p Ar project
113 | .Op Fl n Ar node
114 | .Ar appjail-command
115 | .Op Ar args No "..."
116 | .Nm
117 | .Cm run-cmd
118 | .Op Fl CHPs
119 | .Op Fl p Ar project
120 | .Op Fl n Ar node
121 | .Ar command
122 | .Op Ar args No "..."
123 | .Nm
124 | .Cm run-director
125 | .Op Fl CHPs
126 | .Op Fl p Ar project
127 | .Op Fl n Ar node
128 | .Ar director-command
129 | .Op Ar args No "..."
130 | .Pp
131 | .Nm
132 | .Cm run-script
133 | .Op Fl p Ar project
134 | .Ar run-script
135 | .Op Ar args No "..."
136 | .Nm
137 | .Cm schedule
138 | .Ar lock-file
139 | .Ar args No "..."
140 | .Pp
141 | .Nm
142 | .Cm set-director
143 | .Ar project
144 | .Ar director-file
145 | .Nm
146 | .Cm set-env
147 | .Ar project
148 | .Ar env-file
149 | .Pp
150 | .Sh DESCRIPTION
151 | LittleJet is an open source, easy-to-use orchestrator for managing, deploying,
152 | scaling and interconnecting FreeBSD jails anywhere in the world.
153 | .Pp
154 | The options are as follows:
155 | .Pp
156 | .Bl -tag -width xxx -compact
157 | .It Cm usage
158 | Display usage message and exit.
159 | .Pp
160 | .It Cm version
161 | Display version information about LittleJet.
162 | .Pp
163 | .It Cm add-label Ar project Ar node Ar label Ns = Ns Ar value No "..."
164 | Add a new label to an existing project node.
165 | .Pp
166 | .It Cm del-label Ar project Ar node Ar label
167 | Delete an existing label from the given project node.
168 | .Pp
169 | .It Cm del-labels Ar project Ar node
170 | Delete all labels from the given project node.
171 | .Pp
172 | .It Cm get-label Ar project Ar node Ar label
173 | Get the label value from the project node.
174 | .Pp
175 | .It Cm get-labels Ar project Ar node
176 | Get all labels from the project node.
177 | .Pp
178 | .It Cm add-node Oo Fl T Oc Ar project Ar node
179 | Add a new
180 | .Ar node
181 | to
182 | .Ar project Ns "."
183 | By default, this subcommand connects to the node and runs the
184 | .Xr true 1
185 | command to check if everything is OK unless the
186 | .Fl T
187 | flag is set.
188 | .Pp
189 | .It Cm copy-nodes Oo Fl O Oc Ar src-project Ar dst-project Oo Ar node Oc
190 | Copy nodes from
191 | .Ar src-project
192 | to
193 | .Ar dst-project Ns "."
194 | .Pp
195 | If the
196 | .Fl O
197 | flag is set, nodes in
198 | .Ar dst-project
199 | are deleted first before nodes in
200 | .Ar src-project
201 | are copied.
202 | .Pp
203 | If a
204 | .Ar node
205 | is specified, it is only copied instead of all nodes.
206 | .Pp
207 | .It Cm del-node Ar project Ar node
208 | Remove
209 | .Ar node
210 | from
211 | .Ar project Ns "."
212 | This also destroys the project remotely.
213 | .Pp
214 | .It Cm del-nodes Ar project
215 | Like
216 | .Cm del-node
217 | but for each node of
218 | .Ar project Ns "."
219 | .Pp
220 | .It Cm copy Oo Fl fN Oc Ar project Ar new-project
221 | Copy
222 | .Ar project
223 | as
224 | .Ar new-project Ns "."
225 | .Pp
226 | By default, nodes are copied unless the
227 | .Fl N
228 | flag is set.
229 | .Pp
230 | If the
231 | .Fl f
232 | flag is set and
233 | .Ar new-project
234 | exists, it will be overwritten.
235 | .Pp
236 | .It Cm create Oo Fl f Oc Ar project Oo Ar director-file Oc
237 | Create a new project.
238 | .Pp
239 | This subcommand first determines the directory of the Director file and makes a
240 | complete copy. That new copy is the new project directory. By default, the Director
241 | file is
242 | .Do Pa appjail-director.yml Dc Ns ,
243 | assuming one exists in the current working directory.
244 | .Pp
245 | An error is returned if the project to be created exists unless
246 | .Fl f
247 | is set.
248 | .Pp
249 | .It Cm destroy Ar project
250 | Destroys the nodes of the given project and the project itself. It will destroy
251 | the project remotely on each node, which means that all its jails will be
252 | destroyed.
253 | .Pp
254 | Note that this subcommand will attempt to destroy jails, but if the current jail
255 | fails to be stopped or destroyed, this subcommand continues without problems and
256 | ignores those jails.
257 | .Pp
258 | .It Cm get-nodes Ar project
259 | Get a list of the current nodes for the given project.
260 | .Pp
261 | .It Cm get-projects
262 | Get a list of current projects.
263 | .Pp
264 | .It Cm rename Ar project Ar new-project
265 | Rename a project.
266 | .Pp
267 | Technically, this subcommand first copies
268 | .Ar project
269 | as
270 | .Ar new-project
271 | and then copies the nodes of
272 | .Ar project Ns "."
273 | Once these operations are completed,
274 | .Ar project
275 | is removed.
276 | .Pp
277 | .It Cm show Oo Ar project Oc
278 | Displays information about
279 | .Ar project
280 | or all projects if none are specified.
281 | .Pp
282 | .It Cm run-appjail Oo Fl CHPs Oc Oo Fl p Ar project Oc Oo Fl n Ar node Oc Ar appjail-command Oo Ar args No "..." Oc
283 | .It Cm run-cmd Oo Fl CHPs Oc Oo Fl p Ar project Oc Oo Fl n Ar node Oc Ar command Oo Ar args No "..." Oc
284 | .It Cm run-director Oo Fl CHPs Oc Oo Fl p Ar project Oc Oo Fl n Ar node Oc Ar director-command Oo Ar args No "..." Oc
285 | Run an
286 | .Xr appjail 1
287 | subcommand with
288 | .Cm run-appjail Ns ,
289 | a Director subcommand with
290 | .Cm run-director Ns ,
291 | or simply a shell command on the remote system with
292 | .Cm run-cmd Ns "."
293 | .Pp
294 | If
295 | .Fl p
296 | and
297 | .Fl n
298 | are not set, the given command is executed on each node in each project.
299 | .Pp
300 | For each execution of the
301 | .Cm run-director
302 | subcommand, it sets the
303 | .Ev DIRECTOR_PROJECT
304 | and
305 | .Ev LITTLEJET_NODE
306 | environment variables.
307 | .Pp
308 | .Bl -tag -width xx
309 | .It Fl C
310 | Since the
311 | .Xr ssh 1 Ns "'s"
312 | .Fl t
313 | flag is set, the output will be altered, so
314 | .Xr sansi 1
315 | is used to remove those control characters.
316 | .Pp
317 | Note that this command must put stdout/stderr into a buffer before displaying it.
318 | .It Fl H
319 | By default, a header is displayed to see where the next host should execute the
320 | given command, unless this flag is set.
321 | .It Fl P
322 | The given command is executed linearly on each node, but with this flag it is executed
323 | in parallel on each node. This flag is meaningless when the
324 | .Fl p
325 | and
326 | .Fl n
327 | parameters are set since the given command is executed as if this flag were not set.
328 | .It Fl s
329 | Exits without success when the given command fails. This only makes sense when the
330 | .Fl P
331 | flag is not set.
332 | .Pp
333 | Note that this command must put stdout/stderr into a buffer before displaying it.
334 | .It Fl p Ar project
335 | Run the given command in the given project. If the
336 | .Fl n
337 | parameter is not set, the command is run on each node in this project.
338 | .It Fl n Ar node
339 | Run the given command on the given node. If the
340 | .Fl p
341 | parameter is not set, the command is executed in each project but using this node.
342 | .El
343 | .Pp
344 | .It Cm run-script Oo Fl p Ar project Oc Ar run-script Oo Ar args No "..." Oc
345 | Run the given RunScript on all projects or on the specific project if
346 | .Fl p
347 | is set.
348 | .Pp
349 | .It Cm schedule Ar lock-file Ar args No "..."
350 | If there is another process locking
351 | .Ar lock-file Ns ,
352 | this subcommand will exit immediately and successfully. This subcommand is very
353 | useful when run with a tool like
354 | .Xr cron 8 Ns "."
355 | .Pp
356 | .It Cm set-director Ar project Ar director-file
357 | Set a new Director file by overwriting an existing one in the project directory.
358 | .Pp
359 | .It Cm set-env Ar project Ar env-file
360 | Copy new environment file by overwriting an existing one in the project directory.
361 | .El
362 | .Sh RUNSCRIPTS
363 | .Bl -tag -width xxx
364 | .It Cm deploy.all
365 | Deploy the project to all nodes in parallel.
366 | .It Cm deploy.all.seq
367 | Deploy the project to all nodes sequentially.
368 | .It Cm deploy.each
369 | For each run, deploy to any of the nodes.
370 | .It Cm deploy.once
371 | Deploy a project to a node if it is not already deployed to any of them.
372 | .It Cm deploy.random
373 | Deploy a project to a randomly chosen node.
374 | .It Cm deploy.scale Oo Fl m Ar min Oc Oo Fl M Ar max Oc Oo Fl r Ar rctl-rules Oc Oo Fl S Ar number Oc Oo Fl t Ar scale-type Oc Oo Fl T Ar number Oc
375 | Scale the project.
376 | .Pp
377 | Note that this RunScript can and will destroy the project regardless of the node.
378 | .Pp
379 | .Bl -tag -width xx
380 | .It Fl m Ar min
381 | Minimum of replicas. By default is
382 | .Sy 1 Ns "."
383 | .It Fl M Ar max
384 | Maximum replicas that the project can replicate when auto-scaling. By default
385 | is the same than the total of nodes in the given project.
386 | .It Fl r Ar rctl-rules
387 | Space-separated list of
388 | .Xr rctl 8
389 | rules. Each item is a key and value pair separated by
390 | .Sy = Ns "."
391 | The key is an
392 | .Xr rctl 8
393 | resource. You can use the suffixes described in
394 | .Xr humanize_number 3
395 | for resources that work with bytes and any resources with numbers without suffixes.
396 | Regardless of whether the resource works with bytes or any other unit, the value must
397 | be greater than
398 | .Sy 0 Ns "."
399 | .Pp
400 | Note that the value is interpreted based on the scale type set by the
401 | .Fl t
402 | parameter.
403 | .Pp
404 | Each node must pre-configure
405 | .Sy kern.racct.enable=1
406 | in its
407 | .Xr loader.conf 5
408 | before using any rctl(8) rules.
409 | .It Fl S Ar number
410 | Stabilization window.
411 | .Pp
412 | After there are no more targets
413 | .Pq projects or jails
414 | that fail the
415 | .Xr rctl 8
416 | rules, this RunScript will destroy the rest of the nodes up to the minimum defined
417 | by the
418 | .Fl m
419 | parameter. This parameter will sleep the current process at the given number if
420 | it is greater than
421 | .Sy 0
422 | before re-performing the entire operation of this RunScript.
423 | .Pp
424 | The default is
425 | .Sy 30 Ns "."
426 | .It Fl t Ar scale-type
427 | How the value defined by the
428 | .Xr rctl 8
429 | rule in the
430 | .Fl r
431 | parameter should be interpreted to check whether a target
432 | .Pq project or jail
433 | passes the test or not.
434 | .Bl -tag -width xx
435 | .It Cm any-jail
436 | Fails if any of the jails have a metric greater than or equal to the limit defined
437 | by the
438 | .Fl r
439 | parameter.
440 | .Pp
441 | This is the default.
442 | .It Cm any-project
443 | Fails if the metric total for all jails in the same project is greater
444 | than or equal to the limit defined by the
445 | .Fl r
446 | parameter.
447 | .It Cm average
448 | Calculates the average of the metric for all jails in the same project and fails
449 | if it is greater than or equal to the limit defined by the
450 | .Fl r
451 | parameter.
452 | .It Cm percent-jail Ns = Ns Ar percent
453 | Calculates the percentage of the limit defined by the
454 | .Fl r
455 | parameter and fails if it is greater than or equal to the current metric. For example, if you define a rule as
456 | .Dq vmemoryuse=512m
457 | and a percentage of
458 | .Sy 60 Ns ,
459 | the value will be
460 | .Sy 307
461 | .Po rounded Pc Ns ,
462 | so this test fails if the current metric is greater than or equal to
463 | .Sy 307 Ns "."
464 | .It Cm percent-project Ns = Ns Ar percent
465 | Like
466 | .Cm percent-jail Ns ,
467 | but first get the metric total for all jails in the same project.
468 | .El
469 | .It Fl T Ar number
470 | After deploying to a new node due to a failed test of an
471 | .Xr rctl 8
472 | rule against another target
473 | .Po project or jail Pc Ns ,
474 | sleep up to the number defined by this parameter.
475 | .Pp
476 | The default is
477 | .Sy 15 Ns "."
478 | .El
479 | .It Cm deploy.single Ar node
480 | Deploy a project to the given node.
481 | .It Cm vpn.wg.client Oo Fl j Ar jail Oc Oo Fl n Ar node Oc Oo Fl s Ar suffix Oc Oo Fl v Ar virtual-network Oc Ar vpn
482 | Create a
483 | .Do Connector Dc Ns ,
484 | a jail with a VPN client connected to the VPN server that can forward packets between
485 | a service in another jail and the VPN.
486 | .Pp
487 | A jail that wants to be exposed to the VPN must configure
488 | .Sy vpn.wg.client.include.me
489 | as one of its labels. Also required is
490 | .Sy vpn.wg.client.from.port Ns ,
491 | which is the port the load balancer will use to send packets. Other labels are
492 | .Sy vpn.wg.client.target.port
493 | which by default, when not defined, is the same as
494 | .Sy vpn.wg.client.from.port Ns ,
495 | and finally,
496 | .Sy vpn.wg.client.proto Ns ,
497 | the service protocol, which by default is
498 | .Sy tcp Ns "."
499 | .Pp
500 | You must first deploy the VPN server using the RunScript
501 | .Sy vpn.wg.server Ns .
502 | After deployment,
503 | .Ar vpn
504 | should point to that node. This RunScript also does not include the project even
505 | if you pass an existing one, but it is recommended to pass at least one non-existent
506 | project as
507 | .Sy ignore
508 | at least so as not to execute this RunScript several times up to the total number
509 | of projects that the manager has.
510 | .Pp
511 | This RunScript is designed to be executed more than once. If you run again after
512 | a previous successful deployment, this RunScript will check if the jail that has
513 | the service exists and if not, it will destroy the connector and remove the peer
514 | from the VPN. It will also check if the jail that has the service has changed its
515 | IPv4 address of the Virtual Network and if so it will update the IPv4 that the
516 | connector uses to send packets to that jail to finally reload the
517 | .Xr pf 4
518 | rules.
519 | .Pp
520 | Nodes can be excluded using the
521 | .Sy vpn.wg.client.exclude
522 | label. See
523 | .Cm add-label
524 | for more details. Note that this label is ignored when
525 | .Fl n
526 | is set.
527 | .Pp
528 | .Bl -tag -width xx
529 | .It Fl j Ar jail
530 | .Ar vpn Ns "'s"
531 | jail.
532 | .Pp
533 | The default is
534 | .Sy littlejet-server-wg Ns "."
535 | .It Fl n Ar node
536 | Work only on this node.
537 | .It Fl s Ar suffix
538 | The
539 | .Dq Connector
540 | is a jail with a VPN client and needs a name. The jail name will be
541 | .Sy - Ns ,
542 | where
543 | .Sy
544 | defaults to
545 | .Sy -wg Ns "."
546 | .It Fl v Ar virtual-network
547 | AppJail virtual network created by
548 | .Xr appjail-network 1 Ns "."
549 | By default, this is undefined, so
550 | .Xr appjail-quick 1
551 | will use the
552 | .Sy AUTO_*
553 | network parameters to create one if it doesn't exist.
554 | .El
555 | .It Cm vpn.wg.client.destroy Oo Fl j Ar jail Oc Oo Fl n Ar node Oc Oo Fl s Ar suffix Oc Ar vpn
556 | Destroy all already deployed connectors. This also requires the VPN server to be
557 | deployed for this RunScript to remove the association between the connectors and
558 | the peers.
559 | .Pp
560 | The
561 | .Sy vpn.wg.client.exclude
562 | node label can be used to ignore the given node, but note that this is ignored when
563 | .Fl n
564 | is set.
565 | .It Cm vpn.wg.server Oo Fl E Oc Oo Fl j Ar jail-name Oc Oo Fl p Ar port Oc Oo Fl n Ar network Oc Oo Fl m Ar mtu Oc Oo Fl P Ar seconds Oc Oo Fl v Ar virtual-network Oc Fl e Ar endpoint Ar node
566 | .Pp
567 | Create a VPN server on
568 | .Ar node Ns "."
569 | .Pp
570 | See also
571 | .Lk https://github.com/DtxdF/LittleJet-wg-makejail "VPN server, VPN client and Load balancer"
572 | .Pp
573 | It is necessary to load
574 | .Xr if_wg 4
575 | on each node
576 | .Pq including the VPN server
577 | before using this and other
578 | .Sy vpn.wg.*
579 | RunScripts. Add it to your
580 | .Xr loader.conf 5
581 | file to load it at boot.
582 | .Pp
583 | .Bl -tag -width xx
584 | .It Fl E
585 | By default, the port will be exposed unless this flag is set.
586 | .It Fl j Ar jail-name
587 | Jail name where the VPN server will be.
588 | .Pp
589 | The default is
590 | .Sy littlejet-server-wg Ns "."
591 | .It Fl p Ar port
592 | VPN server port. By default is
593 | .Sy 51820 Ns "."
594 | .It Fl n Ar network
595 | VPN address.
596 | .It Fl m Ar mtu
597 | See
598 | .Sy MTU
599 | in
600 | .Xr wg-quick 8 Ns "."
601 | .It Fl P Ar seconds
602 | See
603 | .Sy PersistentKeepalive
604 | in
605 | .Xr wg 8 Ns "."
606 | .It Fl v Ar virtual-network
607 | AppJail virtual network created by
608 | .Xr appjail-network 1 Ns "."
609 | By default, this is undefined, so
610 | .Xr appjail-quick 1
611 | will use the
612 | .Sy AUTO_*
613 | network parameters to create one if it doesn't exist.
614 | .It Fl e Ar endpoint
615 | IP address of the VPN node.
616 | .Pp
617 | Note that deploying the VPN server, connectors, and/or load balancer on the same
618 | node will not work if you use the external IP address of the VPN node.
619 | .Pp
620 | See
621 | .Lk https://www.openbsd.org/faq/pf/rdr.html#reflect "Redirection and Reflection"
622 | .El
623 | .It Cm vpn.wg.server.destroy Oo Fl j Ar jail-name Oc Ar node
624 | Destroy the VPN jail
625 | .Ar jail-name Ns ,
626 | which by default is
627 | .Sy littlejet-server-wg Ns ,
628 | on node
629 | .Ar node Ns "."
630 | .Pp
631 | If you want to destroy all the load balancers, connectors and the VPN server, you
632 | must run the corresponding RunScripts in that order.
633 | .It Cm vpn.wg.load-balancer.pen Oo Fl eHhrsWP Oc Oo Fl b Ar seconds Oc Oo Fl c Ar number Oc Oo Fl J Ar jail Oc Oo Fl m Ar number Oc Oo Fl n Ar node Oc Oo Fl p Ar port Oc Oo Fl S Ar suffix Oc Oo Fl T Ar seconds Oc Oo Fl t Ar seconds Oc Oo Fl q Ar backlog Oc Oo Fl v Ar virtual-network Oc Oo Fl x Ar number Oc Ar vpn Ar node
634 | Create a jail with a load balancer that also provides failover and a VPN client
635 | that connects to the VPN server
636 | .Ar vpn
637 | for each service that wants to load balance on
638 | .Ar node Ns "."
639 | .Pp
640 | Jails that want to load balance must have the
641 | .Sy vpn.wg.load-balancer.pen.include.me
642 | label and at least
643 | .Sy vpn.wg.client.from.port
644 | configured.
645 | .Sy vpn.wg.client.client.proto
646 | will be used if set.
647 | .Pp
648 | There are other node labels that are more useful depending on the load balancing
649 | algorithm you choose.
650 | .Sy vpn.wg.load-balancer.pen.max Ns ,
651 | .Sy vpn.wg.load-balancer.pen.hard Ns ,
652 | .Sy vpn.wg.load-balancer.pen.weight
653 | and
654 | .Sy vpn.wg.load-balancer.pen.prio Ns "."
655 | See
656 | .Sy server
657 | in
658 | .Xr penctl 1
659 | for more details.
660 | .Pp
661 | Nodes can be excluded using the
662 | .Sy vpn.wg.load-balancer.pen.exclude
663 | label. See
664 | .Cm add-label
665 | for more details. Note that this label is ignored when
666 | .Fl n
667 | is set.
668 | .Pp
669 | Remember that
670 | .Xr pen 1
671 | is an L4 load balancer, so if all nodes are dead or the service is not provided
672 | on any node, the client will see empty replies or just errors.
673 | .Pp
674 | This RunScript is designed to be executed more than once. If run after a successful
675 | deployment, it will update the current list of servers to include more, the same,
676 | or fewer nodes than the previous deployment, but note that this list is updated
677 | dynamically and if you restart
678 | .Xr pen 1
679 | or the jail that once you have it, it will start with the server list you started
680 | with the first time. Since the process will be repeated, the list will be up-to-date.
681 | .Pp
682 | .Bl -tag -width xx
683 | .It Fl e
684 | Expose the service provided by the load balancer.
685 | .Pp
686 | For each load balancer created and when the
687 | .Fl e
688 | flag is set, the port is incremented, so for example, if there are two services
689 | to load balance and the initial port is
690 | .Sy 1234 Ns ,
691 | then the first load balancer will use
692 | .Sy 1234
693 | and the second
694 | .Sy 1235 Ns "."
695 | .It Fl H
696 | See
697 | .Fl H
698 | in
699 | .Xr penctl 1 Ns "."
700 | .It Fl h
701 | See
702 | .Fl h
703 | in
704 | .Xr pen 1 Ns "."
705 | .It Fl r
706 | See
707 | .Fl r
708 | in
709 | .Xr pen 1 Ns "."
710 | .It Fl s
711 | See
712 | .Fl s
713 | in
714 | .Xr pen 1 Ns "."
715 | .It Fl W
716 | See
717 | .Fl W
718 | in
719 | .Xr pen 1 Ns "."
720 | .It Fl P
721 | See
722 | .Sy prio
723 | in
724 | .Xr penctl 1 Ns "."
725 | .It Fl b Ar seconds
726 | See
727 | .Fl b
728 | in
729 | .Xr pen 1 Ns "."
730 | .Pp
731 | The default is
732 | .Sy 6 Ns "."
733 | .It Fl c Ar number
734 | See
735 | .Fl c
736 | in
737 | .Xr pen 1 Ns "."
738 | .Pp
739 | The default is
740 | .Sy 2048 Ns "."
741 | .It Fl J Ar jail
742 | .Ar vpn Ns "'s"
743 | jail.
744 | .Pp
745 | The default is
746 | .Sy littlejet-server-wg Ns "."
747 | .It Fl m Ar number
748 | See
749 | .Fl m
750 | in
751 | .Xr pen 1 Ns "."
752 | .Pp
753 | The default is
754 | .Sy 256 Ns "."
755 | .It Fl n Ar node
756 | Work only on this node.
757 | .It Fl p Ar port
758 | Listening port.
759 | .Pp
760 | The default is
761 | .Sy 1234 Ns "."
762 | .It Fl S Ar suffix
763 | The load balancer is a jail that needs a name. The jail name will be
764 | .Sy - Ns ,
765 | where
766 | .Sy
767 | defaults to
768 | .Sy -lb Ns "."
769 | .It Fl T Ar seconds
770 | See
771 | .Fl T
772 | in
773 | .Xr pen 1 Ns "."
774 | .Pp
775 | The default is
776 | .Sy 6 Ns "."
777 | .It Fl t Ar seconds
778 | See
779 | .Fl t
780 | in
781 | .Xr pen 1 Ns "."
782 | .Pp
783 | The default is
784 | .Sy 10 Ns "."
785 | .It Fl q Ar backlog
786 | See
787 | .Fl q
788 | in
789 | .Xr pen 1 Ns "."
790 | .Pp
791 | The default is
792 | .Sy 500 Ns "."
793 | .It Fl v Ar virtual-network
794 | AppJail virtual network created by
795 | .Xr appjail-network 1 Ns "."
796 | By default, this is undefined, so
797 | .Xr appjail-quick 1
798 | will use the
799 | .Sy AUTO_*
800 | network parameters to create one if it doesn't exist.
801 | .It Fl x Ar number
802 | See
803 | .Fl x
804 | in
805 | .Xr pen 1 Ns "."
806 | .Pp
807 | The default is
808 | .Sy 500 Ns "."
809 | .El
810 | .It Cm vpn.wg.load-balancer.pen.destroy Oo Fl J Ar jail Oc Oo Fl n Ar node Oc Oo Fl S Ar suffix Oc Ar vpn Ar node
811 | Destroy all already deployed load-balancers. This also requires the VPN server to
812 | be deployed for this RunScript to remove the association between the load-balancers
813 | and the peers.
814 | .Pp
815 | The
816 | .Sy vpn.wg.load-balancer.pen.exclude
817 | node label can be used to ignore the given node, but note that this is ignored when
818 | .Fl n
819 | is set.
820 | .El
821 | .Sh FILES
822 | .Bl -tag -width xxxx
823 | .It Pa ~/.config/littlejet/config.conf
824 | See
825 | .Xr littlejet.conf 5 Ns "."
826 | .It Pa %%PREFIX%%/share/littlejet/files/lib.subr
827 | Subroutines primarily used by RunScripts.
828 | .El
829 | .Sh EXIT STATUS
830 | .Ex -std
831 | .Sh SEE ALSO
832 | .Xr appjail 1
833 | .Xr sysexits 3
834 | .Xr littlejet.conf 5
835 | .Lk https://github.com/DtxdF/director "AppJail Director"
836 | .Sh AUTHORS
837 | .An Jesús Daniel Colmenares Oviedo Aq Mt DtxdF@disroot.org
838 |
--------------------------------------------------------------------------------
/share/man/man5/littlejet.conf.5:
--------------------------------------------------------------------------------
1 | .\"Copyright (c) 2024, Jesús Daniel Colmenares Oviedo
2 | .\"All rights reserved.
3 | .\"
4 | .\"Redistribution and use in source and binary forms, with or without
5 | .\"modification, are permitted provided that the following conditions are met:
6 | .\"
7 | .\"* Redistributions of source code must retain the above copyright notice, this
8 | .\" list of conditions and the following disclaimer.
9 | .\"
10 | .\"* Redistributions in binary form must reproduce the above copyright notice,
11 | .\" this list of conditions and the following disclaimer in the documentation
12 | .\" and/or other materials provided with the distribution.
13 | .\"
14 | .\"* Neither the name of the copyright holder nor the names of its
15 | .\" contributors may be used to endorse or promote products derived from
16 | .\" this software without specific prior written permission.
17 | .\"
18 | .\"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | .\"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | .\"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | .\"DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | .\"FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | .\"DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | .\"SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | .\"CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | .\"OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | .\"OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | .Dd July 2, 2024
29 | .Dt LITTLEJET.CONF 5
30 | .Os
31 | .Sh NAME
32 | .Nm ~/.config/littlejet/config.conf
33 | .Nd Configuration parameters for LittleJet
34 | .Sh DESCRIPTION
35 | The
36 | .Xr littlejet 1
37 | utility uses some defaults that are safe for most environments, but you can change
38 | any parameters described below to adapt it to your needs.
39 | .Nm
40 | is a file that contains the parameters that control the operation of
41 | .Xr littlejet 1 "."
42 | Note that this file is an
43 | .Xr sh 1
44 | script.
45 | .Sh PATH PARAMETERS
46 | .Bl -tag -width xxxxx
47 | .It PREFIX
48 | Root prefix used by other directories.
49 | .Pp
50 | Default:
51 | .Em %%PREFIX%%
52 | .Sh SYSTEM DIRECTORIES PARAMETERS
53 | .Bl -tag -width xxxxx
54 | .It SHAREDIR
55 | Location of shared files.
56 | .Pp
57 | Default:
58 | .Em ${PREFIX}/share/littlejet
59 | .It FILESDIR
60 | Location of files used by LittleJet.
61 | .Pp
62 | Default:
63 | .Em ${SHAREDIR}/files
64 | .It DATADIR
65 | Location of files generated by LittleJet.
66 | .Pp
67 | This is currently not used except as a shortcut for other parameters. See
68 | .Sy LITTLEJETDIR
69 | which does perform this function.
70 | .Pp
71 | Default:
72 | .Em ${PREFIX}/littlejet
73 | .It LIB_SUBR
74 | Subroutines primarily used by RunScripts.
75 | .Pp
76 | Default:
77 | .Em ${FILESDIR}/lib.subr
78 | .El
79 | .Sh USER DIRECTORIES PARAMETERS
80 | .Bl -tag -width xxxxx
81 | .It LITTLEJETDIR
82 | Location of files generated by LittleJet.
83 | .Pp
84 | Default:
85 | .Em ${HOMEDIR}/.littlejet
86 | .It PROJECTSDIR
87 | Location of projects.
88 | .Pp
89 | Default:
90 | .Em ${LITTLEJETDIR}/projects
91 | .It RUNSCRIPTS
92 | Space-separated list of directories where RunScripts are located.
93 | .Pp
94 | Note that this works similarly to the
95 | .Ev PATH
96 | environment variable, where the first match wins. This is very useful when you
97 | want to use your own RunScript that has the same name as the one shipped by
98 | LittleJet. Of course, this is a double-edged sword: if you have scripts that
99 | depend on specific parameters or some specific behavior that your RunScript
100 | doesn't provide, those scripts can and probably will fail.
101 | .Pp
102 | Default:
103 | .Em ${LITTLEJETDIR}/runscripts ${SHAREDIR}/runscripts
104 | .It SOCKETSDIR
105 | Location of SSH sockets when multiplexing.
106 | .Pp
107 | Default:
108 | .Em ${LITTLEJETDIR}/sockets
109 | .It NODESDIR
110 | Location of nodes.
111 | .Pp
112 | Default:
113 | .Em ${LITTLEJETDIR}/nodes
114 | .El
115 | .Sh SSH-RELATED PARAMETERS
116 | .Bl -tag -width xxxxx
117 | .It CONTROLPATH
118 | See
119 | .Sy ControlPath
120 | in
121 | .Xr ssh_config 5 Ns "."
122 | .Pp
123 | Default:
124 | .Em %r.%h.%p
125 | .It CONTROLPERSIST
126 | See
127 | .Sy ControlPersist
128 | in
129 | .Xr ssh_config 5 Ns "."
130 | .Pp
131 | Default:
132 | .Em 8m
133 | .It CPIGNORE
134 | Exclusion file used by
135 | .Xr cpdup 1 Ns "."
136 | .Pp
137 | Default:
138 | .Em ${FILESDIR}/cpignore
139 | .El
140 | .Sh LOGGING PARAMETERS
141 | .Bl -tag -width xxxxx
142 | .It DEBUG
143 | Enable debug level.
144 | .Pp
145 | Default:
146 | .Em NO
147 | .It SSH_LOGLEVEL
148 | See
149 | .Sy LogLevel
150 | in
151 | .Xr ssh_config 5 Ns "."
152 | .Pp
153 | Default:
154 | .Em ERROR
155 | .El
156 | .Sh REMOTE PARAMETERS
157 | .Bl -tag -width xxxxx
158 | .It REMOTE_DATADIR
159 | Location of files generated by LittleJet on the remote system.
160 | .Pp
161 | Default:
162 | .Em ${DATADIR}
163 | .It REMOTE_PROJECTSDIR
164 | Location of projects that are uploaded by LittleJet to the remote system.
165 | .Pp
166 | Default:
167 | .Em ${REMOTE_DATADIR}/projects
168 | .El
169 | .Sh MULTI-PROCESSING PARAMETERS
170 | .Bl -tag -width xxxxx
171 | .It NCPU
172 | Maximum number of jobs running at the same time.
173 | .El
174 | .Sh INTERFACE PARAMETERS
175 | .Bl -tag -width xxxxx
176 | .It SHOW_HEALTHCHECKERS
177 | Displays the healthcheckers when the jail has one or more defined.
178 | .Pp
179 | Default:
180 | .Em 0
181 | .It SHOW_LIMITS
182 | Displays the limits when the jail has one or more defined.
183 | .Pp
184 | Default:
185 | .Em 0
186 | .It SHOW_STATS
187 | Displays the stats of each jail when the system has
188 | .Xr rctl 8
189 | enabled.
190 | .Pp
191 | Default:
192 | .Em 0
193 | .El
194 | .Sh SEE ALSO
195 | .Xr cpdup 1
196 | .Xr littlejet 1
197 | .Xr ssh_config 5
198 | .Sh AUTHORS
199 | .An Jesús Daniel Colmenares Oviedo Aq Mt DtxdF@disroot.org
200 |
--------------------------------------------------------------------------------