comparison sbin/fjail @ 128:3dcae0e91769

Move all admin scripts into the "sbin" folder
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 17 Oct 2019 09:09:13 +0200
parents bin/fjail@52523a19797e
children 6be3742d21f7
comparison
equal deleted inserted replaced
127:37737f225887 128:3dcae0e91769
1 #!/bin/sh
2 # -*- indent-tabs-mode: nil; -*-
3 : 'A very minimal BSD Jail management tool.
4
5 :Author: Franz Glasner
6 :Copyright: (c) 2019 Franz Glasner.
7 All rights reserved.
8 :License: BSD 3-Clause "New" or "Revised" License.
9 See LICENSE for details.
10 If you cannot find LICENSE see
11 <https://opensource.org/licenses/BSD-3-Clause>
12 :ID: @(#)@@PKGORIGIN@@ $HGid$
13
14 '
15
16 set -eu
17
18 VERSION="@@VERSION@@"
19
20 USAGE='
21 USAGE: fjail [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ]
22
23 OPTIONS:
24
25 -V Print the program name and version number to stdout and exit
26
27 -h Print this help message to stdout and exit
28
29 COMMANDS:
30
31 datasets [-u] PARENT CHILD
32
33 Create ZFS datasets to be used within a jail
34
35 PARENT must exist already and CHILD must not exist.
36
37 -u Do not automatically mount newly created datasets
38
39 privs MOUNTPOINT
40
41 Adjust some Unix privileges to mounted jail datasets
42
43 populate MOUNTPOINT BASETXZ
44
45 Populate the jail directory in MOUNTPOINT with the base system in BASETXZ
46
47 copy SOURCE-DATASET DEST-DATASET
48
49 Copy a tree of ZFS datasets with "zfs send -R" and "zfs receive".
50 Note that the destination dataset must not exist already.
51
52 -u Do not automatically mount received datasets
53
54 ENVIRONMENT:
55
56 All environment variables that affect "zfs" are effective also.
57
58 DESCRIPTION:
59
60 All commands with the exception of "populate" require ZFS as
61 filesystem.
62 '
63
64
65 # Reset to standard umask
66 umask 0022
67
68
69 #
70 # "datasets" -- create the ZFS dataset tree
71 #
72 # command_datasets [ -u ] parent-dataset child-dataset
73 #
74 # -u do not automatically mount newly created datasets
75 #
76 command_datasets() {
77 # parent ZFS dataset -- child ZFS dataset name
78 local _pds _cds
79 # and its mount point
80 local _pmp _get _dummy
81 # full name of the dataset
82 local _ds
83 # dynamic ZFS options
84 local _zfsopts
85
86 _zfsopts=""
87 while getopts "u" _opt ; do
88 case ${_opt} in
89 u)
90 # do not mount newly created datasets
91 _zfsopts="${_zfsopts} -u"
92 ;;
93 \?|:)
94 return 2;
95 ;;
96 esac
97 done
98 shift $((OPTIND-1))
99 OPTIND=1
100
101 _pds="$1"
102 if [ -z "${_pds}" ]; then
103 echo "ERROR: no parent dataset given" >&2
104 return 2
105 fi
106 _get=$(zfs get -H mountpoint "${_pds}" 2>/dev/null) || { echo "ERROR: dataset \`${_pds}' does not exist" >&2; return 1; }
107 IFS=$'\t' read _dummy _dummy _pmp _dummy <<EOF
108 ${_get}
109 EOF
110 case "${_pmp}" in
111 none)
112 echo "ERROR: dataset \`${_pds}' has no mountpoint" >&2
113 return 1
114 ;;
115 legacy)
116 echo "ERROR: dataset \`${_pds}' has a \`${_mp}' mountpoint" >&2
117 return 1
118 ;;
119 *)
120 # VOID
121 ;;
122 esac
123 _cds="$2"
124 if [ -z "${_cds}" ]; then
125 echo "ERROR: no child dataset given" >&2
126 return 2
127 fi
128 _ds="${_pds}/${_cds}"
129 echo "Resulting new root dataset is \`${_ds}' at mountpoint \`${_pmp}/${_cds}'"
130 if zfs get -H mountpoint "${_ds}" >/dev/null 2>/dev/null; then
131 echo "ERROR: dataset \`${_ds}' does already exist" >&2
132 return 1
133 fi
134 zfs create ${_zfsopts} -o atime=off "${_ds}"
135 zfs create ${_zfsopts} -o sync=disabled -o setuid=off "${_ds}/tmp"
136 zfs create ${_zfsopts} "${_ds}/usr"
137 zfs create ${_zfsopts} "${_ds}/var"
138 zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/audit"
139 zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/cache"
140 zfs create ${_zfsopts} -o exec=off -o setuid=off -o compression=off "${_ds}/var/cache/pkg"
141 zfs create ${_zfsopts} -o exec=off -o setuid=off -o compression=off "${_ds}/var/crash"
142 zfs create ${_zfsopts} -o exec=off -o setuid=off "${_ds}/var/db"
143 zfs create ${_zfsopts} -o exec=on -o setuid=off "${_ds}/var/db/pkg"
144 zfs create ${_zfsopts} -o readonly=on -o exec=off -o setuid=off "${_ds}/var/empty"
145 zfs create ${_zfsopts} -o exec=off -o setuid=off -o primarycache=metadata "${_ds}/var/log"
146 zfs create ${_zfsopts} -o exec=off -o setuid=off -o atime=on "${_ds}/var/mail"
147 zfs create ${_zfsopts} -o sync=disabled -o exec=off -o setuid=off -o compression=off -o primarycache=all "${_ds}/var/run"
148 zfs create ${_zfsopts} -o sync=disabled -o setuid=off "${_ds}/var/tmp"
149 }
150
151
152 #
153 # "populate" -- populate the datasets with content from a FreeBSD base.txz
154 #
155 # command_populate mountpoint basetxz
156 #
157 command_populate() {
158 # MOUNTPOINT -- base.txz
159 local _mp _basetxz
160
161 _mp="$1"
162 _basetxz="$2"
163
164 if [ -z "${_mp}" ]; then
165 echo "ERROR: no mountpoint given" >&2
166 return 2
167 fi
168 if [ -z "${_basetxz}" ]; then
169 echo "ERROR: no base.txz given" >&2
170 return 2
171 fi
172 if [ ! -d "${_mp}" ]; then
173 echo "ERROR: mountpoint \`${_mp}' does not exist" >&2
174 return 1
175 fi
176 if [ ! -r "${_basetxz}" ]; then
177 echo "ERROR: file \`${_basetxz}' is not readable" >&2
178 return 1
179 fi
180
181 tar -C "${_mp}" --exclude=./var/empty -xJp -f "${_basetxz}" || { echo "ERROR: tar encountered errors" >&2; return 1; }
182 }
183
184
185 #
186 # "copy" -- ZFS copy of datasets
187 #
188 # command_copy source-dataset destination-dataset
189 #
190 command_copy() {
191 # source dataset -- destination dataset
192 local _source _dest
193 # dynamic ZFS options
194 local _zfsopts
195
196 _zfsopts=""
197 while getopts "u" _opt ; do
198 case ${_opt} in
199 u)
200 # do not mount newly created datasets
201 _zfsopts="${_zfsopts} -u"
202 ;;
203 \?|:)
204 return 2;
205 ;;
206 esac
207 done
208 shift $((OPTIND-1))
209 OPTIND=1
210
211 _source="$1"
212 if [ -z "${_source}" ]; then
213 echo "ERROR: no source dataset given" >&2
214 return 2
215 fi
216 _dest="$2"
217 if [ -z "${_dest}" ]; then
218 echo "ERROR: no source dataset given" >&2
219 return 2
220 fi
221 zfs send -R -n -v ${_source} || { echo "ERROR: ZFS operation failed in no-op mode" >&2; return 1; }
222 zfs send -R "${_source}" | zfs receive ${_zfsopts} "${_dest}" || { echo "ERROR: ZFS operation failed" >&2; return 1; }
223 }
224
225
226 #
227 # "privs" -- adjust privileges
228 #
229 # To be used when all ZFS datasets are mounted.
230 #
231 command_privs() {
232 # mountpoint
233 local _mp _d
234
235 _mp="$1"
236 if [ -z "${_mp}" ]; then
237 echo "ERROR: no mountpoint given" >&2
238 return 2
239 fi
240 if [ ! -d "${_mp}" ]; then
241 echo "ERROR: directory \`${_mp}' does not exist" >&2
242 return 1
243 fi
244 for _d in tmp var/tmp ; do
245 chmod 01777 "${_mp}/${_d}"
246 done
247 chown root:mail "${_mp}/var/mail"
248 chmod 0775 "${_mp}/var/mail"
249 }
250
251
252 #
253 # Global option handling
254 #
255 while getopts "Vh" _opt ; do
256 case ${_opt} in
257 V)
258 echo "fjail v${VERSION} (rv:@@HGREVISION@@)"
259 exit 0
260 ;;
261 h)
262 echo "${USAGE}"
263 exit 0
264 ;;
265 \?)
266 exit 2;
267 ;;
268 *)
269 echo "ERROR: option handling failed" >&2
270 exit 2
271 ;;
272 esac
273 done
274
275 #
276 # Reset the Shell's option handling system to prepare for handling
277 # command-local options.
278 #
279 shift $((OPTIND-1))
280 OPTIND=1
281
282 test $# -gt 0 || { echo "ERROR: no command given" >&2; exit 2; }
283
284 command="$1"
285 shift
286
287 case "${command}" in
288 datasets)
289 command_datasets "$@"
290 ;;
291 privs)
292 command_privs "$@"
293 ;;
294 populate)
295 command_populate "$@"
296 ;;
297 copy)
298 command_copy "$@"
299 ;;
300 *)
301 echo "ERROR: unknown command \`${command}'" >&2
302 exit 2
303 ;;
304 esac