Scripts and configs of my jails on freebsd

Content of directory /root/bin

List all jails. Example:

jlist
+--------------+-----------+--------------------+---------------------------------------------------------------+
| runs/stopped | version   | autostart ip/ip    | note                                                          |
+--------------+-----------+--------------------+---------------------------------------------------------------+
| astZf1       | 14.3-R    | 81.xx.xx.52/27     | Asterisk worker                                               |
| dbTest       | 14.2-R    | 192.168.144.157/24 |                                                               |
| dbZFS        | 14.2-R    | 192.168.144.154/24 | Starší mysql s innodb, po 5 vteřinách přerušuje dlouhé dotazy |
| dbZFSino     | 14.2-R-p3 | 192.168.144.156/24 | Nejnovější mariadb s innoDB                                   |
| dbZFSsata    | 14.2-R    | 192.168.144.153/24 |                                                               |
| pkg          | 14.3-R    | 192.168.144.152/24 | Created 2025-06-17 11:14:12                                   |
| ucet         | 14.3-R    | 192.168.144.155/24 |                                                               |
+--------------+-----------+--------------------+---------------------------------------------------------------+
Starting order: dbZFSino,dbZFSsata,dbZFS,ucet
Alternative user scripts located in ~/bin :jedit,jdestroy,jcreate
/root/bin/jlist
#!/usr/bin/env ruby
 
require 'json'
require '~/bin/ArrayToTable'
 
def mark_running(t);  return "#{C_LIGHT_CYAN}#{t}#{C_NC}"; end
 
def mark_autostart(t);  return "#{C_YELLOW}#{t}#{C_NC}";end
 
# it expect that each jail config is located in /jails/JAILNAME/c.conf
 
existing_jails= `grep -h '^[^#]' /jails/*/c.conf 2> /dev/null| grep -h -E '[[:space:]]*[[:alnum:]][[:space:]]*\\{' | tr -d '\\ \\t{'`.split
 
running_jails=JSON.parse(`jls --libxo json`)['jail-information']['jail'].map{ |hsh| hsh['hostname'] }
 
autostart_jails=`sysrc -n jail_list`.strip.split
 
array_table=[[ mark_running("runs")+"/stopped","version", mark_autostart("autostart ip")+"/ip", "note" ]]
 
existing_jails.each{|j|
  config=File.read("/jails/#{j}/c.conf")
  ip=/\s*\$ip\s*=\s*"([^"]*)"/.match(config)
  ip= ip ? ip[1] : "-"
  note=/\s*\$note\s*=\s*"([^"]*)"/.match(config)
  note = note ? note[1] : "-"
  if File.exist?("/jails/#{j}/rootfs/bin/freebsd-version")
    freeBSDversion=`/jails/#{j}/rootfs/bin/freebsd-version -u`.sub("RELEASE","R").sub("CURRENT","C").sub("STABLE","S").sub("BETA","B").strip
  else
    freeBSDversion="-"
  end
  running=running_jails.include?(j)
  is_autostart=autostart_jails.include?(j)
 
  array_table << [  running ? mark_running(j) : j,freeBSDversion,is_autostart ? mark_autostart(ip) : ip, note ]
}
 
print array_table.to_table
 
puts "Starting order: #{autostart_jails.join(",")}"
 
print "Alternative user scripts located in ~/bin :"
 
 
bin_directory_content =Dir.entries("/root/bin").delete_if {|x| x.start_with?(".")}
blacklisted_bin_content=%w(ArrayToTable.rb jlist odkladiste jmore jailCreateEpair.sh)
 
puts  (bin_directory_content-blacklisted_bin_content).join(",")
/root/bin/ArrayToTable.rb
# It prints centered table in console, does not count color changing sequences as characters.
class Array
  def to_table(header: true)
    column_sizes = self.reduce([]) do |lengths, row|
      row.each_with_index.map{|iterand, index| [lengths[index] || 0, iterand.to_s.gsub(/\e[^m]*m/,'').length].max}
    end
    head = '+' + column_sizes.map{|column_size| '-' * (column_size + 2) }.join('+') + '+'
    puts head
 
    to_print = self.clone
    if (header == true)
      header = to_print.shift
      print_table_data_row(column_sizes, header)
      puts head
    end
    to_print.each{ |row| print_table_data_row(column_sizes, row) }
    puts head
  end
 
  private
  def print_table_data_row(column_sizes, row)
    row = row.fill(nil, row.size..(column_sizes.size - 1))
    row = row.each_with_index.map{|v, i| v = v.to_s + ' ' * (column_sizes[i] - v.to_s.gsub(/\e[^m]*m/,'').length)}
    puts '| ' + row.join(' | ') + ' |'
  end
end
 
 
C_NC="\e[0m" # No Color
C_BLACK="\e[0;30m";C_GRAY="\e[1;30m";C_RED="\e[0;31m";C_LIGHT_RED="\e[1;31m";C_GREEN="\e[0;32m";C_LIGHT_GREEN="\e[1;32m";C_BROWN="\e[0;33m";C_YELLOW="\e[1;33m";C_BLUE="\e[0;34m";C_LIGHT_BLUE="\e[1;34m";C_PURPLE="\e[0;35m";C_LIGHT_PURPLE="\e[1;35m";C_CYAN="\e[0;36m";C_LIGHT_CYAN="\e[1;36m";C_LIGHT_GRAY="\e[0;37m";C_WHITE="\e[1;37m"

I prefer nice predictable names of interface pairs so had to write script for it. Generated names of epairs will be renamed.

/root/bin/jailCreateEpair.sh
#! /bin/sh
 
jailName="$1"
 
# ifconfig returns new epair name as outpout
epairA="$(ifconfig epair create)"
# the second intervace has got b at the end
epairB="${epairA%?}b"
 
# We rename the interface from the generated name to be te same like jail's name
# The maxium lenght of network interface is about 15 character which souhld be enough
ifconfig $epairA name A$jailName
ifconfig A$jailName up
ifconfig bridge0 addm A$jailName up
 
ifconfig $epairB name B$jailName
# The line below would only work if called from  exec.created section
#ifconfig eth0 vnet $jailName up
# insipration from https://forums.freebsd.org/threads/jails-vnet-freebsd-mastery-multiple-interfaces.70356/
 
# to list only epair interfaces you can you can use
# ifconfig -g epair -l

opens config of one ore more jails in vim

/root/bin/jedit
#! /bin/sh
vim_arguments=""
while test $# -gt 0
do
 vim_arguments="$vim_arguments  /jails/$1/c.conf"
 shift
done
 
vim -p $vim_arguments

Creates new jail.

/root/bin/jcreate
#! /bin/sh
 
jailName="$1"
 
mkdir -p /jails/$jailName
 
if zfs list zroot/jails/$jailName
then
  echo " Dataset $jailName already exists !"
  exit
else
 ls -al /usr/freebsd-dist/MANIFEST
 echo "I will install freeBSD version which was issued by the day the file above was last changed."
 read -p "If you want to instlal newer version, remove the content of dirctory /usr/freebsd-dist/ before continuing.  " REPLY
 zfs create -o mountpoint=/jails/$jailName zroot/jails/$jailName
 # zfs set recordsize=8K zroot/jails/$jailName
 cd  /jails/$jailName
 # adresář rootfs to samo vytvoří
 bsdinstall jail rootfs
 datum=`date "+%Y-%m-%d %H:%M:%S"`
 printf "$jailName{\n\$note = \"Created $datum\";\n\$ip = \"192.168.144.XXX/24\";\n}">/jails/$jailName/c.conf
 
 echo "Doing update patches"
 
 freebsd-update -b /jails/$jailName/rootfs fetch
 freebsd-update -b /jails/$jailName/rootfs install
 
 echo "Do not forget to change ip adress in /jails/$jailName/c.conf"
 echo "Remember to setup firewall."
 echo "Install apps for example by \"pkg install screen vim mc\" inside."
fi

destroys a jail

/root/bin/jdestroy
#! /bin/sh
 
jailName="$1"
 
 
if ! zfs list zroot/jails/$jailName
then
  echo " dataset zroot/jails/$jailName does not exists "
  exit
fi  
service jail stop $jailName
 
read -p "Are you sure you want to delete $jailName ? " REPLY
echo    # (optional) move to a new line
if [ "$REPLY" == "Y" ] || [ "$REPLY" == "y" ] 
then
  zfs destroy zroot/jails/$jailName
  sleep 2
  rmdir /jails/$jailName
fi

Jail config files

/etc/jail.conf
  $bridge = "bridge0";
  $gateway = "XX.XX.XX.XX";
  # PATH/HOSTNAME
  path = "/jails/${name}/rootfs";
  host.hostname = "${name}";
 
  # VNET/VIMAGE
  vnet;
  vnet.interface = "B$name";
  exec.prestart = "/root/bin/jailCreateEpair.sh $name";
  # have to remove the mask part of ip in order to by able to use the ip in firewall rules
  exec.prestart += "(echo -n '# $note\next_if = \"B$name\"\next_ip = \"' && echo -n '${ip}'|cut -d \"/\" -f 1|tr -d '\n' && echo -n '\"\n')> ${path}/etc/pf_local_definitions.conf";
  # I mount a common config file direcotry for shared configs and settings
  exec.prestart += "mkdir -p ${path}/etc/ovps_shared";
  exec.prestart += "mount_nullfs -o ro /etc/ovps_shared ${path}/etc/ovps_shared";
 
 
  exec.start    = "ifconfig B$name ${ip} up";
  exec.start    += "route add -net XX.XX.XX.XX/29  -interface B$name || true";
  exec.start    += "route add -net XX.XX.XX.XX/28  -interface B$name || true";
  exec.start    += "route add -net 192.168.145.0/24  81.31.45.55";
  exec.start    += "route add default ${gateway}";
  exec.start    += "/bin/sh /etc/rc";
 
 
 
  exec.poststop = "ifconfig bridge0 deletem A$name";
  exec.poststop += "ifconfig A$name destroy";
  exec.release = "umount -t nullfs ${path}/etc/ovps_shared";
  # STARTUP/LOGGING
  exec.stop  = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";
 
  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 5;
 
.include "/jails/*/c.conf";

Each jail has got very smal own config. For example

/jails/pkg/c.conf
pkg{
$note = "Created 2025-06-17 11:14:12";
$ip = "192.168.144.152/24";
}
 
freebsd.txt · Last modified: 2025/06/26 01:18 by root