README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
?????????????????
? what is zoa ?
?????????????????
zoa is the best config management tool. it's the
best because it's the simplest. it's written in shell, has
a few nice little VERY USEFUL helpers, and doesn't go out
of its way to make you hate yourself.
zoa requires access to a git repo, though it may be used
in local development mode.
zoa is intended for human-scale deployment, and generally
works best if you're not trying to manage a crazy complicated
fleet of systems between multiple teams. zoa is for small,
tight-knit teams who want to keep it all in their heads with
minimal effort.
it's also for individuals who like understanding their systems,
and hate maintaining them when things break for no reason.
why did you write this?
i manage systems with config management tools for a living.
i know how impossible they all are firsthand, i have 6+ years
of painful, painful experience. i wrote zoa to use on my personal
systems, because i hate the primary config management tools a lot.
also, i <3 shell :D
~~ zoa doesn't pretend ~~
chef & ansible & puppet & salt all pretend to be
fully idempotent & declarative, but leave
actual declarative-ness and idempotence as
an exercise to the user.
zoa doesn't pretend. zoa is not idempotent. zoa
is not declarative. it assumes your state changes
over time, and zoa makes it easy to keep up with those
changes. you won't have to look at horrible doc websites
or commit a week to learning a god
damned
stupid
DSL
other config management systems make their users
feel stupid.
zoa doesn't. zoa uses shell scripts like every
single linux distro has used for millenia. it
has exactly zero dependencies besides zoa itself.
zoa adheres to standards, and uses well-known
distro conventions.
zoa is easy. and simple. and honestly,
quite a smol guy
the other tools want to do a lot - search across
your nodes, deploy via their tooling, automate
testing, manage AWS resources?!?!?
zoa is only concerned with _managing configuration_
_on servers_
and nothing else. forever.
the other tools break constantly because they
try to do everything.
zoa breaks rarely because it does almost nothing.
!!!!!!!!!!!!!!!!!!
! QUICKSTART !
!!!!!!!!!!!!!!!!!!
- ~ - understand: there are three components - ~ -
everything you need to know:
1: the utility
2: env vars and helpers
3: the layout
--- 1: the utility ---
simply run the "zoa" tool on all of your systems. it's a simple binary (XXX: or
shell script?) that can be installed trivially. it expects to run as root, and
it should run on a cron/timer/whatever schedule at whatever interval you want.
install:
wget -O https://j3s.sh/zoa/zoa # TODO
mv zoa /usr/local/sbin
chmod +x /usr/local/sbin/zoa
design guideline: all configuration data comes from a git repository.
the nodes themselves keep no state at all.
to run zoa:
zoa https://git.j3s.sh/j3s/config main
^ ^ ^
zoa, duh. git repo git ref (branch or tag)
zoa will clone the repo+branch specified in your config (or attempt to
fetch it, if it's already cloned) to /var/lib/zoa/<repo>/<branch>
zoa will then execute /var/lib/zoa/<repo>/<branch>/main
if you run "zoa" with 0 arguments, it will execute if it finds exactly 1
repo/branch combo in /var/lib/zoa - if you run zoa this way, it will not
clone the remote repo. this can be very useful for local testing. note
that the next run of zoa with a repo+branch will wipe any changes you
make in /var/lib.
that's it, you've configured your server! set up a cronjob/systemd timer to
run zoa on a schedule, if that's your thing. or just login and run it
periodically. you could even have the first zoa run set up a cronjob that runs it
on a schedule, to make your life even easier.
👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀👀
--- 2: environment variables and helper functions ---
in zoa, you write plain POSIX shell. Why?
- posix shell is productive
- posix shell is portable
- posix shell is hard to make too complicated
ENVIRONMENT VARIABLES
before zoa runs, it sets a few standard environment variables for your usage.
here are _all of them_.
displayed values are from my dev system
caveats are marked with *
$PATH - the search path for binaries. this is hardcoded.
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
$ARCH - the name of the hardware type on which the system is running
ARCH=x86_64
$HOSTNAME - the name of this node.
HOSTNAME=nostromo.j3s.sh
* there is no shortname vs fqdn standard, so this env var may vary
by distro
$OS - the operating system name
OS=Linux
* on BSD systems, this var is a lot more useful
* if you want your distro, take a look at OS_RELEASE_ID
$RELEASE - the current release level of the operating system implementation
RELEASE=5.19.5-arch1-1
! WARNING: ALL $OS_RELEASE_* VARIABLES ARE UNRELIABLE !
be sure to check for their existence before
using them. ty~ :3
$OS_RELEASE_ID - short uncapitalized name of your distro
OS_RELEASE_ID=arch
* see above warning
$OS_RELEASE_VERSION_ID - version of your distro, if applicable
OS_RELEASE_VERSION_ID= # note that arch has no version ID
* see above warning
TODO: expose hardware info, cpu cores, ip address, memory availability, etc
HELPER FUNCTIONS
zoa has some little helper functions for common operations.
zoa-file
usage:
zoa-file example.conf /etc/example.conf optional-command
^ ^ ^
source destination executed if the file changes
description:
zoa-file takes a file from $zoa_root/files/ and places it on-disk
somewhere. if the file is changed, a command is optionally executed.
this can be very useful for things like reloading configurations.
when you think about zoa-file, just think of it as regular cp, with an
extra argument for running arbitrary code if the cp does anything.
examples:
zoa-file motd /etc/motd
zoa-file nginx/j3s.sh /etc/nginx/conf.d/j3s.sh systemctl reload nginx
todo:
add a template processing step of some kind?
zoa-script
usage:
zoa-script scriptname
^
script to execute
description:
zoa-script executes the given script from $zoa_root/scripts/
examples:
zoa-script nginx
zoa-script 420/69
--- 3: the layout ---
ah. finally. how do i lay out my zoa repo?
here's the canonical fs layout:
main <-- entrypoint
files/ <-- arbitrary text files you'd like to place
scripts/ <-- arbitrary shell scripts
you need at least main! everything else is optional.
main is your entrypoint. if your whole vibe is just doing basic configuration
of nodes, you _could_ put literally everything in main and just be done with it.
however, you may want main to be a little more complex:
case $NODENAME in
git.j3s.sh)
. ./
esac
scripts/ contains arbitrary shell scripts. you can organize them how you'd like.
files/ contains text files (no big files or binaries pls) that you might be
interested in placing on hosts. these can be accessed with the zoa_file function.
*~~~ warts ~~~*
* zoa doesn't support function definitions YET
* zoa reserved words must be all-on-one-line atm
BONUS SECTION! :3 :3 <3 :3
common config management patterns to zoa
### install a package,
### but only if the distro is debian
zoa
if [ "$DISTRO" = "debian" ]; then
apt install -y cowsay
fi
ansible
- name: add package
when: ansible_facts['os_family'] == "Debian"
package:
name: 'cowsay'
state: present
### place sshd_config,
### and reload ssh when it changes
ansible
# handlers/main.yml
- name: restart-sshd
service:
name: sshd
state: restarted
# tasks/main.yml
- name: Configure sshd
template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: 0644
notify: restart-sshd
zoa
zoa-file sshd /etc/ssh/sshd_config systemctl restart sshd
chown root:root /etc/ssh/sshd_config
chmod 0644 /etc/ssh/sshd_config
### append an iptables rule to the input chain
ansible
- name: do the thing
iptables:
chain: INPUT
protocol: tcp
destination_port: '22'
ctstate: NEW
syn: match
jump: ACCEPT
zoa
rule='INPUT --protocol tcp --dport 69 --jump ACCEPT'
iptables --check $rule || iptables --append $rule