diff --git a/conftest.py b/conftest.py
index 1f12df0385ba72e2817362e6b9b10fe57d14ed39..7b25fc764af7c8b860e286f5a80d2e65e0d13854 100644
--- a/conftest.py
+++ b/conftest.py
@@ -35,7 +35,7 @@ def pytest_configure(config):
     # Set target configuration
     target = config.getoption("-T")
     if target not in targets:
-        raise Exception("No configuration for target: {}".format(target))
+        raise Exception(f"No configuration for target: {target}")
     setattr(config, "target_config", targets[target])
 
 
@@ -43,7 +43,7 @@ def pytest_runtest_setup(item):
     board = item.config.target_config["board"]
     for boards in item.iter_markers(name="board"):
         if board not in boards.args:
-            pytest.skip("test is not compatible with target: {}".format(board))
+            pytest.skip(f"test is not compatible with target: {board}")
     for conn in ("serial", "wan", "lan1", "lan2"):
         if item.get_closest_marker(conn) is not None and conn not in item.config.target_config:
-            pytest.skip("test requires connection: {}".format(conn))
+            pytest.skip(f"test requires connection: {conn}")
diff --git a/imgs/base-alpine/bin/wait4network b/imgs/base-alpine/bin/wait4network
index d4fd2d8d45e53a13b41443ea9b53062e326796e5..57d5c9c9b47e8a8c89d84f55226aed13bdcc6dcb 100755
--- a/imgs/base-alpine/bin/wait4network
+++ b/imgs/base-alpine/bin/wait4network
@@ -1,8 +1,6 @@
 #!/bin/sh
 # This file is part of NSFarm
 # Call this script to block execution untill there is default route present.
-
-echo waiting for network
 while ! ip route | grep -q default; do
 	sleep 1
 done
diff --git a/nsfarm/board/__init__.py b/nsfarm/board/__init__.py
index 2d455675431f8c35f8bc38b5dbbfb67188384b09..4aa711ed8f1c60e62acc35d47ba32d103ed9bc27 100644
--- a/nsfarm/board/__init__.py
+++ b/nsfarm/board/__init__.py
@@ -13,5 +13,5 @@ def get_board(config):
     }
     board = config.target_config["board"]
     if board not in boards:
-        raise Exception("Unknown or unsupported board: {}".format(board))
+        raise Exception(f"Unknown or unsupported board: {board}")
     return boards[board](config.getoption("-T"), config.target_config)
diff --git a/nsfarm/board/_board.py b/nsfarm/board/_board.py
index b4c9befa2a9ccc8ca25e93289de9ce6421130799..178959ed5fef514b8471678b597e69d4261e6f06 100644
--- a/nsfarm/board/_board.py
+++ b/nsfarm/board/_board.py
@@ -25,8 +25,10 @@ class Board:
         self.config = target_config
         # Open serial console to board
         self._serial = serial.Serial(self.config['serial'], 115200)
+        #self._fdlogging = cli.FDLogging(self._serial, logging.getLogger(f"{__package__}[{target}]"))
+        #self._pexpect = fdpexpect.fdspawn(self._fdlogging.socket())
         self._pexpect = fdpexpect.fdspawn(self._serial)
-        self._pexpect.logfile_read = cli.PexpectLogging(logging.getLogger('{}[{}]'.format(__package__, target)))
+        self._pexpect.logfile_read = cli.PexpectLogging(logging.getLogger(f"{__package__}[{target}]"))
         # Set board to some known state
         self.reset(True)  # Hold in reset state
 
@@ -101,12 +103,13 @@ class Board:
         miniterm.set_rx_encoding(MINITERM_ENCODING)
         miniterm.set_tx_encoding(MINITERM_ENCODING)
 
+        key_quit = serial.tools.miniterm.key_description(miniterm.exit_character)
+        key_menu = serial.tools.miniterm.key_description(miniterm.menu_character)
+        key_help = serial.tools.miniterm.key_description('\x08')
+
         sys.stderr.write('\n')
-        sys.stderr.write('--- Miniterm on {p.name} ---\n'.format(p=miniterm.serial))
-        sys.stderr.write('--- Quit: {0} | Menu: {1} | Help: {1} followed by {2} ---\n'.format(
-            serial.tools.miniterm.key_description(miniterm.exit_character),
-            serial.tools.miniterm.key_description(miniterm.menu_character),
-            serial.tools.miniterm.key_description('\x08')))
+        sys.stderr.write(f"--- Miniterm on {miniterm.serial.name} ---\n")
+        sys.stderr.write(f"--- Quit: {key_quit} | Menu: {key_menu} | Help: {key_help} followed by {key_menu} ---\n")
 
         miniterm.start()
         miniterm.join()
diff --git a/nsfarm/cli.py b/nsfarm/cli.py
index dbad4d39d1b8da4bc19b9ce3f33a703a1af99852..f1ebdd6fb29e3a1f8d40154db22eea0b43d32c6b 100644
--- a/nsfarm/cli.py
+++ b/nsfarm/cli.py
@@ -27,7 +27,8 @@ def run_exit_code_zero(exit_code):
     This checks if exit code is zero and raises exception if it is not.
     """
     if exit_code != 0:
-        raise Exception("Command exited with non-zero code: {}".format(exit_code))
+        raise Exception(f"Command exited with non-zero code: {exit_code}")
+    return 0
 
 
 class Cli:
@@ -45,6 +46,7 @@ class Cli:
 
     def prompt(self, **kwargs):
         """Follow output until prompt is reached and parse it.  Exit code is returned.
+        All keyword arguments are passed to pexpect's expect call.
         """
         raise NotImplementedError
 
@@ -69,12 +71,14 @@ class Cli:
         self.expect_exact(cmd)
         self.expect_exact(["\r\n", "\n\r"])
 
-    def run(self, cmd="", exit_code=run_exit_code_zero, **kwargs):
+    def run(self, cmd: typing.Optional[str] = "",
+            exit_code: typing.Optional[typing.Callable[[int], None]] = run_exit_code_zero,
+            **kwargs):
         """Run given command and follow output until prompt is reached and return exit code with optional automatic
         check. This is same as if you would call cmd() and prompt() while checking exit_code.
 
         cmd: command to be executed
-        exit_code: lambda function verifying exit code or None to skip default check
+        exit_code: function verifying exit code or None to skip default check
         All other key-word arguments are passed to prompt call.
 
         Returns result of exit_code function or exit code of command if exit_code is None.
@@ -102,17 +106,17 @@ class Shell(Cli):
 
     This is tested to handle busybox and bash.
     """
+    _COLUMNS_NUM = 800
     _SET_NSF_PROMPT = r"export PS1='nsfprompt:$(echo -n $?)\$ '"
     _NSF_PROMPT = r"(\r\n|\n\r|^)nsfprompt:([0-9]+)($|#) "
     _INITIAL_PROMPTS = [
         r"(\r\n|\n\r|^).+? ($|#) ",
         r"(\r\n|\n\r|^)bash-.+?($|#) ",
         r"(\r\n|\n\r|^)root@[a-zA-Z0-9_-]*:",
-        r"(\r\n|\n\r|^).*root@turris.*#",  # TODO this is weird dual prompt from lxd ssh
+        r"(\r\n|\n\r|^).*root@turris.*#",  # Note: this is weird dual prompt from lxd ssh
         _NSF_PROMPT,
     ]
     # TODO compile prompt regexp to increase performance
-    # TODO the width of terminal is limited and command function fails with long commands
 
     def __init__(self, pexpect_handle, flush=True):
         super().__init__(pexpect_handle, flush=flush)
@@ -121,11 +125,19 @@ class Shell(Cli):
         self.expect(self._INITIAL_PROMPTS)
         # Now sanitize prompt format
         self.run(self._SET_NSF_PROMPT)
+        # And set huge number of columns to fix any command we throw at it
+        self.run(f"stty columns {self._COLUMNS_NUM}")
 
     def prompt(self, **kwargs):
         self.expect(self._NSF_PROMPT, **kwargs)
         return int(self.match(2))
 
+    def command(self, cmd=""):
+        # 20 characters are removed as those are approximately for prompt
+        if len(cmd) >= (self._COLUMNS_NUM - 20):
+            raise Exception("Command probably won't fit to terminal. Split it or increase number of columns.")
+        return super().command(cmd)
+
     def file_read(self, path):
         """Read file trough shell.
 
@@ -133,7 +145,7 @@ class Shell(Cli):
 
         This returns bytes with content of file from path.
         """
-        self._sh.run("base64 '{}'".format(path))
+        self._sh.run(f"base64 '{path}'")
         return base64.b64decode(self._sh.output())
 
     def file_write(self, path, content):
@@ -142,11 +154,11 @@ class Shell(Cli):
         path: path to file to be written
         content: bytes to be written to it
         """
-        self._sh.sendline("base64 --decode > '{}'".format(path))
+        self._sh.sendline(f"base64 --decode > '{path}'")
         self._sh.sendline(base64.b64encode(content))
         self._sh.sendeof()
         if self._sh.prompt() != 0:
-            raise Exception("Writing file failed with exit code: {}".format(self._sh.prompt()))
+            raise Exception(f"Writing file failed with exit code: {self._sh.prompt()}")
 
     @property
     def output(self):
diff --git a/nsfarm/lxd/__main__.py b/nsfarm/lxd/__main__.py
index 07e8d4ac03034c33c3ebc55d9137a94004df2c97..c5aadc2c2f85e4f74e88e878cdb01d5690c81228 100644
--- a/nsfarm/lxd/__main__.py
+++ b/nsfarm/lxd/__main__.py
@@ -14,7 +14,8 @@ def parser(parser):
     clean.add_argument(
         'DELTA',
         nargs='?',
-        default='1w',
+        default=dateutil.relativedelta.relativedelta(weeks=1),
+        type=parse_deltatime,
         help="""Time delta for how long image should not be used to be cleaned (removed). In default if not specified
         '1w' is used. Format is expect to be a number with suffix. Supported suffixes are m(inute), h(our), d(ay) and
         w(eek).
@@ -75,14 +76,14 @@ def parse_deltatime(spec):
             delta += trans[char](int(num))
             num = ""
         else:
-            raise ValueError("Invalid character '{}' in time delta specification: {}".format(char, spec))
+            raise ValueError(f"Invalid character '{char}' in time delta specification: {spec}")
     return delta
 
 
 def op_clean(args, _):
     """Handler for command line operation clean
     """
-    removed = utils.clean(parse_deltatime(args.DELTA), dry_run=args.dry_run)
+    removed = utils.clean(args.DELTA, dry_run=args.dry_run)
     if removed:
         print('\n'.join(removed))
     sys.exit(0)
diff --git a/nsfarm/lxd/_lxd.py b/nsfarm/lxd/_lxd.py
index 37a968ece4c20da3b064cbb86ed612f27e577127..2b477deb92380731dbf8adca418bfde2dec31285 100644
--- a/nsfarm/lxd/_lxd.py
+++ b/nsfarm/lxd/_lxd.py
@@ -15,7 +15,7 @@ local = None
 
 
 def _profile_device(profile, check_func):
-    return True not in {check_func(dev) for _, dev in profile.devices.items()}
+    return any(check_func(dev) for dev in profile.devices.values())
 
 
 def connect():
@@ -36,12 +36,12 @@ def connect():
         for name in REQUIRED_PROFILES:
             if not local.profiles.exists(name):
                 # TODO better exception
-                raise Exception("Missing required LXD profile: {}".format(name))
+                raise Exception(f"Missing required LXD profile: {name}")
         root = local.profiles.get(ROOT_PROFILE)
         internet = local.profiles.get(INTERNET_PROFILE)
-        if _profile_device(root, lambda dev: dev["type"] == "disk"):
+        if not _profile_device(root, lambda dev: dev["type"] == "disk"):
             # TODO better exception
             raise Exception("nsfarm-root does not provide disk device")
-        if _profile_device(internet, lambda dev: dev["type"] == "nic" and dev["name"] == "internet"):
+        if not _profile_device(internet, lambda dev: dev["type"] == "nic" and dev["name"] == "internet"):
             # TODO better exception
             raise Exception("nsfarm-internet does not provide appropriate nic")
diff --git a/nsfarm/lxd/container.py b/nsfarm/lxd/container.py
index 1f3cd9d485716f3ee1f3cb618432517b9a14d3d1..9483d432badb40f89e6c10ba4678a0e5ce279d22 100644
--- a/nsfarm/lxd/container.py
+++ b/nsfarm/lxd/container.py
@@ -28,10 +28,10 @@ class Container:
         self._devices = tuple(devices)
         self._dir_path = os.path.join(IMGS_DIR, img_name)
         self._file_path = self._dir_path + ".sh"
-        self._logger = logging.getLogger("{}[{}]".format(__package__, img_name))
+        self._logger = logging.getLogger(f"{__package__}[{img_name}]")
         # Verify existence of image definition
         if not os.path.isfile(self._file_path):
-            raise Exception("There seems to be no file describing image: {}".format(self._file_path))
+            raise Exception(f"There seems to be no file describing image: {self._file_path}")
         if not os.path.isdir(self._dir_path):
             self._dir_path = None
         # Make sure that we are connected to LXD
@@ -46,10 +46,10 @@ class Container:
         elif parent.startswith("images:"):
             self._parent = _lxd.images.images.get_by_alias(parent[7:])
         else:
-            raise Exception("The file has parent from unknown source: {}: {}".format(parent, self.fpath))
+            raise Exception(f"The file has parent from unknown source: {parent}: {self.fpath}")
         # Calculate identity hash and generate image name
         self._hash = self.__identity_hash()
-        self._image_alias = "nsfarm/{}/{}".format(self._name, self._hash)
+        self._image_alias = f"nsfarm/{self._name}/{self._hash}"
         # Some empty handles
         self._lxd_image = None
         self._lxd_container = None
@@ -107,7 +107,7 @@ class Container:
             image_source["mode"] = "pull"
             image_source["server"] = _lxd.IMAGES_SOURCE
             image_source["alias"] = self._parent.fingerprint
-        container_name = "nsfarm-bootstrap-{}-{}".format(self._name, self._hash)
+        container_name = f"nsfarm-bootstrap-{self._name}-{self._hash}"
         try:
             container = _lxd.local.containers.create({
                 'name': container_name,
@@ -137,13 +137,13 @@ class Container:
                 res = container.execute([IMAGE_INIT_PATH])
                 if res.exit_code != 0:
                     # TODO more appropriate exception and possibly use stderr and stdout
-                    raise Exception("Image initialization failed: {}".format(res))
+                    raise Exception(f"Image initialization failed: {res}")
                 container.files.delete(IMAGE_INIT_PATH)  # Remove init script
             finally:
                 container.stop(wait=True)
             # Create and configure image
             self._lxd_image = container.publish(wait=True)
-            self._lxd_image.add_alias(self._image_alias, "NSFarm image: {}".format(self._name))
+            self._lxd_image.add_alias(self._image_alias, f"NSFarm image: {self._name}")
         finally:
             container.delete()
 
@@ -178,12 +178,12 @@ class Container:
         # container.
 
     def _container_name(self, prefix="nsfarm"):
-        name = "{}-{}-{}".format(prefix, self._name, os.getpid())
+        name = f"{prefix}-{self._name}-{os.getpid()}"
         if _lxd.local.containers.exists(name):
             i = 1
-            while _lxd.local.containers.exists("{}-{}".format(name, i)):
+            while _lxd.local.containers.exists(f"{name}-{i}"):
                 i += 1
-            name = "{}-{}".format(name, i)
+            name = f"{name}-{i}"
         return name
 
     def cleanup(self):
diff --git a/nsfarm/lxd/utils.py b/nsfarm/lxd/utils.py
index d693a70de90a3b777c743ac42e2fb63bbee4c126..15aeb9f4edad8c2b42ade3ee00cd6fb39c10e029 100644
--- a/nsfarm/lxd/utils.py
+++ b/nsfarm/lxd/utils.py
@@ -22,8 +22,8 @@ def clean(delta, dry_run=False):
     since = datetime.today() - delta
 
     removed = list()
-    for img in _lxd.LOCAL.images.all():
-        if next((alias for alias in img.aliases if alias["name"].startswith("nsfarm/")), None) is None:
+    for img in _lxd.local.images.all():
+        if not any(alias.startswith("nsfarm/") for alias in img.aliases):
             continue
         last_used = dateutil.parser.parse(
             # Special time "0001-01-01T00:00:00Z" means never used so use upload time instead
diff --git a/tests/conftest.py b/tests/conftest.py
index 97617ffa6a5deccbe1175e5ed0b89a2d4f1a8378..3371f7382c2e1b8540a37021beb9c9a156ed8498 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -53,7 +53,7 @@ def fixture_board_root_password(request, board_serial):
     Returns configured random password
     """
     password = ''.join(random.choice(string.ascii_lowercase) for i in range(16))
-    board_serial.run("echo 'root:{}' | chpasswd".format(password))
+    board_serial.run(f"echo 'root:{password}' | chpasswd")
     request.addfinalizer(lambda: board_serial.run("passwd --delete root"))
     return password
 
@@ -62,6 +62,8 @@ def fixture_board_root_password(request, board_serial):
 def fixture_client_board(board_serial, board_root_password, lan1_client):
     """Starts client on LAN1 and connect to board using SSH.
     Provides instance of nsfarm.cli.Shell() connected to board shell using SSH trough client container.
+
+    This is prefered over serial console as kernel logs are preferably printed there and that can break CLI machinery.
     """
     # Let's have syslog on serial console as well as kernel log
     #board_serial.command('tail -f /var/log/messages')
@@ -92,7 +94,7 @@ def fixture_lan1_client(lan1):
 # Standard configuration ###############################################################################################
 
 @pytest.fixture(name="basic_isp", scope="module")
-def fixture_basic_isp(client_board, wan):
+def fixture_basic_isp(board, client_board, wan):
     """Basic config we consider general. It provides you with configured WAN.
 
     Returns handle for ISP container on WAN interface.
@@ -106,8 +108,8 @@ def fixture_basic_isp(client_board, wan):
         client_board.run("uci set network.wan.dns='1.1.1.1'")  # TODO configure to ISP
         client_board.run("uci commit network")
         client_board.run("/etc/init.d/network restart")
-        client_board.run("while ! ip route | grep -q default; do sleep 1; done")  # Wait for default route
-        # TODO this does not ensure that we ping gateway but why?
+        client_board.run(f"while ! ip link show {board.wan} | grep -q ' state UP '; do sleep 1; done")
+        time.sleep(3)  # Wait just a bit to ensure that network is up and running
         yield container
         client_board.run("uci set network.wan.proto='none'")
         client_board.run("uci delete network.wan.ipaddr")
diff --git a/tests/network/common.py b/tests/network/common.py
index 3559c2cb1ccdfa80399efcd81c01ae61a7e4791c..884ad1a70e82a19e7e7badc3f3c9c45be3277834 100644
--- a/tests/network/common.py
+++ b/tests/network/common.py
@@ -22,7 +22,7 @@ class InternetTests:
 
         We send only one ICMP packet to not flood and to be quickly done with it (the success takes less than second)
         """
-        client.run("ping -c 1 '{}'".format(server))
+        client.run(f"ping -c 1 '{server}'")
 
     @pytest.mark.parametrize("server", [
         "nic.cz",
@@ -32,6 +32,6 @@ class InternetTests:
     def test_dns(self, client, server):
         """Try to resolve verious domain names.
         """
-        client.run("nslookup '{}'".format(server))
+        client.run(f"nslookup '{server}'")
 
     # TODO more excessive DNS testing
diff --git a/tests/network/test_lan.py b/tests/network/test_lan.py
index cf28dcdca55baa1f3cef63e2641d7371c5697d29..5618ad51c0e4905ea23b568c4e7555b08ec0d454 100644
--- a/tests/network/test_lan.py
+++ b/tests/network/test_lan.py
@@ -7,13 +7,6 @@ from . import common
 # pylint: disable=no-self-use
 
 
-def _apply(client_board):
-    client_board.run("uci commit network")
-    client_board.run("/etc/init.d/network restart")
-    time.sleep(5)  # TODO drop this as this is just to prevent problems with kernel log in console
-    client_board.run("while ! ip route | grep -q default; do sleep 1; done")  # Wait for default route
-
-
 class TestInternet(common.InternetTests):
     """Test WAN with network settings configured statically.
     """
diff --git a/tests/network/test_wan.py b/tests/network/test_wan.py
index 8c7292a5ea2f7fde6fe1e358e6fc0784a6683238..ffe58fd80cc6a4c818a841c0c38634540ce3e5c3 100644
--- a/tests/network/test_wan.py
+++ b/tests/network/test_wan.py
@@ -11,18 +11,12 @@ from . import common
 # TODO: add support for IPV6, currently we only test IPv4
 
 
-def _apply(client_board):
-    client_board.run("uci commit network")
-    client_board.run("/etc/init.d/network restart")
-    client_board.run("while ! ip route | grep -q default; do sleep 1; done")  # Wait for default route
-
-
 class TestStatic(common.InternetTests):
     """Test WAN with network settings configured statically.
     """
 
     @pytest.fixture(scope="class", autouse=True)
-    def client(self, client_board, wan):
+    def client(self, board, client_board, wan):
         """Configure WAN to use static IP
         """
         print("We are in client fixture once")
@@ -33,7 +27,10 @@ class TestStatic(common.InternetTests):
             client_board.run("uci set network.wan.netmask='255.240.0.0'")
             client_board.run("uci set network.wan.gateway='172.16.1.1'")
             client_board.run("uci set network.wan.dns='1.1.1.1'")  # TODO configure to ISP
-            _apply(client_board)
+            client_board.run("uci commit network")
+            client_board.run("/etc/init.d/network restart")
+            client_board.run(f"while ! ip link show {board.wan} | grep -q ' state UP '; do sleep 1; done")
+            time.sleep(3)  # Wait just a bit to ensure that network is up and running
             yield client_board
             client_board.run("uci set network.wan.proto='none'")
             client_board.run("uci delete network.wan.ipaddr")
@@ -54,7 +51,9 @@ class TestDHCP(common.InternetTests):
         with nsfarm.lxd.Container('isp-dhcp', devices=[wan, ]):
             client_board.run("uci set network.wan.proto='dhcp'")
             client_board.run("uci commit network")
-            _apply(client_board)
+            client_board.run("/etc/init.d/network restart")
+            client_board.run("while ! ip route | grep -q default; do sleep 1; done")
+            time.sleep(1)  # Wait just a bit to ensure that network is up and running
             yield client_board
             client_board.run("uci set network.wan.proto='none'")
             client_board.run("uci commit network")
diff --git a/tests/test_bootup.py b/tests/test_bootup.py
index ced4b2461adb56823cfe882be248351897b01909..88f5662f27e73fbf62e81c3157164a9b1eb48b29 100644
--- a/tests/test_bootup.py
+++ b/tests/test_bootup.py
@@ -26,7 +26,7 @@ def test_syslog_ng(client_board):
 def test_processes(client_board, process):
     """Check that various essential processes are running.
     """
-    client_board.run("pgrep -x '{p}' || pgrep -x \"$(which '{p}')\"".format(p=process))
+    client_board.run(f"pgrep -x '{process}' || pgrep -x \"$(which '{process}')\"")
 
 
 def test_lighttpd(client_board):