diff --git a/freebsd/__init__.py b/freebsd/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/freebsd/__init__.py
diff --git a/freebsd/_linuxcontext.py b/freebsd/_linuxcontext.py
new file mode 100644
index 0000000..bbf3ae0
--- /dev/null
+++ b/freebsd/_linuxcontext.py
@@ -0,0 +1,276 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from os import mkdir, environ, chdir
+import platform
+from shutil import copy, rmtree, move
+import subprocess
+import sys
+
+system = platform.system()
+working_directory = sys.path[0]
+
+try:
+    chdir(working_directory)
+    subprocess.call(('pip', 'install', '-r', 'requirements.txt'))
+    if system == 'Linux':
+        copy("/root/agent/misc/vm_renewal", "/usr/local/bin/")
+except:
+    pass  # hope it works
+
+
+import logging
+import fileinput
+import tarfile
+from os.path import expanduser, join, exists
+from glob import glob
+from StringIO import StringIO
+from base64 import decodestring
+from hashlib import md5
+
+
+from ssh import PubKey
+from .network import change_ip_ubuntu, change_ip_rhel
+from context import BaseContext
+
+from twisted.internet import reactor
+
+logging.basicConfig()
+logger = logging.getLogger()
+level = environ.get('LOGLEVEL', 'INFO')
+logger.setLevel(level)
+
+SSH_DIR = expanduser('~cloud/.ssh')
+AUTHORIZED_KEYS = join(SSH_DIR, 'authorized_keys')
+
+STORE_DIR = '/store'
+
+mount_template_linux = (
+    '//%(host)s/%(username)s %(dir)s cifs username=%(username)s'
+    ',password=%(password)s,iocharset=utf8,uid=cloud  0  0\n')
+
+
+distros = {'Scientific Linux': 'rhel',
+           'CentOS': 'rhel',
+           'CentOS Linux': 'rhel',
+           'Debian': 'debian',
+           'Ubuntu': 'debian'}
+if system == 'Linux':
+    distro = distros[platform.linux_distribution()[0]]
+
+
+class Context(BaseContext):
+
+    # http://stackoverflow.com/questions/12081310/
+    # python-module-to-change-system-date-and-time
+    def _linux_set_time(time):
+        import ctypes
+        import ctypes.util
+
+        CLOCK_REALTIME = 0
+
+        class timespec(ctypes.Structure):
+            _fields_ = [("tv_sec", ctypes.c_long),
+                        ("tv_nsec", ctypes.c_long)]
+
+        librt = ctypes.CDLL(ctypes.util.find_library("rt"))
+
+        ts = timespec()
+        ts.tv_sec = int(time)
+        ts.tv_nsec = 0
+
+        librt.clock_settime(CLOCK_REALTIME, ctypes.byref(ts))
+
+    @staticmethod
+    def change_password(password):
+        proc = subprocess.Popen(['/usr/sbin/chpasswd'],
+                                stdin=subprocess.PIPE)
+        proc.communicate('cloud:%s\n' % password)
+
+    @staticmethod
+    def restart_networking():
+        if distro == 'debian':
+            subprocess.call(['/etc/init.d/networking', 'restart'])
+        elif distro == 'rhel':
+            subprocess.call(['/bin/systemctl', 'restart', 'network'])
+            pass
+
+    @staticmethod
+    def change_ip(interfaces, dns):
+        if distro == 'debian':
+            change_ip_ubuntu(interfaces, dns)
+        elif distro == 'rhel':
+            change_ip_rhel(interfaces, dns)
+
+    @staticmethod
+    def set_time(time):
+        Context._linux_set_time(float(time))
+        try:
+            subprocess.call(['/etc/init.d/ntp', 'restart'])
+        except:
+            pass
+
+    @staticmethod
+    def set_hostname(hostname):
+        if distro == 'debian':
+            with open('/etc/hostname', 'w') as f:
+                f.write(hostname)
+        elif distro == 'rhel':
+            for line in fileinput.input('/etc/sysconfig/network',
+                                        inplace=1):
+                if line.startswith('HOSTNAME='):
+                    print 'HOSTNAME=%s' % hostname
+                else:
+                    print line.rstrip()
+
+        with open('/etc/hosts', 'w') as f:
+            f.write("127.0.0.1 localhost\n"
+                    "127.0.1.1 %s\n" % hostname)
+
+        subprocess.call(['/bin/hostname', hostname])
+
+    @staticmethod
+    def mount_store(host, username, password):
+        data = {'host': host, 'username': username, 'password': password}
+        data['dir'] = STORE_DIR
+        if not exists(STORE_DIR):
+            mkdir(STORE_DIR)
+        # TODO
+        for line in fileinput.input('/etc/fstab', inplace=True):
+            if not (line.startswith('//') and ' cifs ' in line):
+                print line.rstrip()
+
+        with open('/etc/fstab', 'a') as f:
+            f.write(mount_template_linux % data)
+
+        subprocess.call('mount -a', shell=True)
+
+    @staticmethod
+    def get_keys():
+        retval = []
+        try:
+            with open(AUTHORIZED_KEYS, 'r') as f:
+                for line in f.readlines():
+                    try:
+                        retval.append(PubKey.from_str(line))
+                    except:
+                        logger.exception(u'Invalid ssh key: ')
+        except IOError:
+            pass
+        return retval
+
+    @staticmethod
+    def _save_keys(keys):
+        print keys
+        try:
+            mkdir(SSH_DIR)
+        except OSError:
+            pass
+        with open(AUTHORIZED_KEYS, 'w') as f:
+            for key in keys:
+                f.write(unicode(key) + '\n')
+
+    @staticmethod
+    def add_keys(keys):
+        new_keys = Context.get_keys()
+        for key in keys:
+            try:
+                p = PubKey.from_str(key)
+                if p not in new_keys:
+                    new_keys.append(p)
+            except:
+                logger.exception(u'Invalid ssh key: ')
+        Context._save_keys(new_keys)
+
+    @staticmethod
+    def del_keys(keys):
+        new_keys = Context.get_keys()
+        for key in keys:
+            try:
+                p = PubKey.from_str(key)
+                try:
+                    new_keys.remove(p)
+                except ValueError:
+                    pass
+            except:
+                logger.exception(u'Invalid ssh key: ')
+        Context._save_keys(new_keys)
+
+    @staticmethod
+    def cleanup():
+        filelist = ([
+            '/root/.bash_history'
+            '/home/cloud/.bash_history'
+            '/root/.ssh'
+            '/home/cloud/.ssh']
+            + glob('/etc/ssh/ssh_host_*'))
+        for f in filelist:
+            rmtree(f, ignore_errors=True)
+
+        subprocess.call(('/usr/bin/ssh-keygen', '-A'))
+
+    @staticmethod
+    def start_access_server():
+        try:
+            subprocess.call(('/sbin/start', 'ssh'))
+        except OSError:
+            subprocess.call(('/bin/systemctl', 'start', 'sshd.service'))
+
+    @staticmethod
+    def append(data, filename, chunk_number, uuid):
+        if chunk_number == 0:
+            flag = "w"
+        else:
+            flag = "a"
+        with open(filename, flag) as myfile:
+            myfile.write(data)
+
+    @staticmethod
+    def update(filename, executable, checksum, uuid):
+        new_dir = working_directory + '.new'
+        old_dir = working_directory + '.old.%s' % uuid
+        with open(filename, "r") as f:
+            data = f.read()
+            local_checksum = md5(data).hexdigest()
+            if local_checksum != checksum:
+                raise Exception("Checksum missmatch the file is damaged.")
+            decoded = StringIO(decodestring(data))
+        try:
+            tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz')
+            tar.extractall(new_dir)
+        except tarfile.ReadError as e:
+            logger.error(e)
+        move(working_directory, old_dir)
+        move(new_dir, working_directory)
+        logger.info("Transfer completed!")
+        reactor.stop()
+
+    @staticmethod
+    def ipaddresses():
+        import netifaces
+        args = {}
+        interfaces = netifaces.interfaces()
+        for i in interfaces:
+            if i == 'lo':
+                continue
+            args[i] = []
+            addresses = netifaces.ifaddresses(i)
+            args[i] = ([x['addr']
+                        for x in addresses.get(netifaces.AF_INET, [])] +
+                       [x['addr']
+                        for x in addresses.get(netifaces.AF_INET6, [])
+                        if '%' not in x['addr']])
+        return args
+
+    @staticmethod
+    def get_agent_version():
+        try:
+            with open('version.txt') as f:
+                return f.readline()
+        except IOError:
+            return None
+
+    @staticmethod
+    def send_expiration(url):
+        import notify
+        notify.notify(url)
diff --git a/freebsd/network.py b/freebsd/network.py
new file mode 100644
index 0000000..b6cb525
--- /dev/null
+++ b/freebsd/network.py
@@ -0,0 +1,139 @@
+import netifaces
+from netaddr import IPNetwork
+import fileinput
+import logging
+import subprocess
+
+logger = logging.getLogger()
+
+interfaces_file = '/etc/network/interfaces'
+ifcfg_template = '/etc/sysconfig/network-scripts/ifcfg-%s'
+
+
+def get_interfaces_linux(interfaces):
+    for ifname in netifaces.interfaces():
+        mac = netifaces.ifaddresses(ifname)[17][0]['addr']
+        conf = interfaces.get(mac.upper())
+        if conf:
+            yield ifname, conf
+
+
+def remove_interfaces_ubuntu(devices):
+    delete_device = False
+
+    for line in fileinput.input(interfaces_file, inplace=True):
+        line = line.rstrip()
+        words = line.split()
+
+        if line.startswith('#') or line == '' or line.isspace() or not words:
+            # keep line
+            print line
+            continue
+
+        if (words[0] in ('auto', 'allow-hotplug') and
+                words[1].split(':')[0] in devices):
+            # remove line
+            continue
+
+        if words[0] == 'iface':
+            ifname = words[1].split(':')[0]
+            if ifname in devices:
+                # remove line
+                delete_device = True
+                continue
+            else:
+                delete_device = False
+
+        if line[0] in (' ', '\t') and delete_device:
+            # remove line
+            continue
+
+        # keep line
+        print line
+
+
+def change_ip_ubuntu(interfaces, dns):
+    data = list(get_interfaces_linux(interfaces))
+
+    for ifname, conf in data:
+        subprocess.call(('/sbin/ifdown', ifname))
+        subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname))
+        subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname,
+                         'down'))
+    remove_interfaces_ubuntu(dict(data).keys())
+
+    with open(interfaces_file, 'a') as f:
+        for ifname, conf in data:
+            ipv4_alias_counter = ipv6_alias_counter = 0
+            f.write('auto %s\n' % ifname)
+            for i in conf['addresses']:
+                ip_with_prefix = IPNetwork(i)
+                prefixlen = ip_with_prefix.prefixlen
+                ip = ip_with_prefix.ip
+                alias = ifname
+                if ip.version == 6:
+                    if ipv6_alias_counter > 0:
+                        alias = '%s:%d' % (ifname, ipv6_alias_counter)
+                    ipv6_alias_counter += 1
+                else:
+                    if ipv4_alias_counter > 0:
+                        alias = '%s:%d' % (ifname, ipv4_alias_counter)
+                    ipv4_alias_counter += 1
+
+                f.write(
+                    'iface %(ifname)s %(proto)s static\n'
+                    '    address %(ip)s\n'
+                    '    netmask %(prefixlen)d\n'
+                    '    gateway %(gw)s\n'
+                    '    dns-nameservers %(dns)s\n' % {
+                        'ifname': alias,
+                        'proto': 'inet6' if ip.version == 6 else 'inet',
+                        'ip': ip,
+                        'prefixlen': prefixlen,
+                        'gw': conf['gw6' if ip.version == 6 else 'gw4'],
+                        'dns': dns})
+    for ifname, conf in data:
+        subprocess.call(('/sbin/ifup', ifname))
+
+
+# example:
+# change_ip_ubuntu({
+#    u'02:00:00:02:A3:E8': {
+#        u'gw4': u'10.1.0.254', 'gw6': '2001::ffff',
+#        u'addresses': [u'10.1.0.84/24', '10.1.0.1/24', '2001::1/48']},
+#    u'02:00:00:02:A3:E9': {
+#        u'gw4': u'10.255.255.1', u'addresses': [u'10.255.255.9']}},
+#    '8.8.8.8')
+
+
+def change_ip_rhel(interfaces, dns):
+    for ifname, conf in get_interfaces_linux(interfaces):
+        subprocess.call(('/sbin/ifdown', ifname))
+        subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname))
+        subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname, 'down'))
+        with open(ifcfg_template % ifname,
+                  'w') as f:
+            f.write('DEVICE=%s\n'
+                    'DNS1=%s\n'
+                    'BOOTPROTO=none\n'
+                    'NM_CONTROLLED=no\n'
+                    'USERCTL=no\n'
+                    'ONBOOT=yes\n' % (ifname, dns))
+            for i in conf['addresses']:
+                ip_with_prefix = IPNetwork(i)
+                ip = ip_with_prefix.ip
+                if ip.version == 6:
+                    f.write('IPV6INIT=yes\n'
+                            'IPV6ADDR=%(ip)s/%(prefixlen)d\n'
+                            'IPV6_DEFAULTGW=%(gw)s\n' % {
+                                'ip': ip,
+                                'prefixlen': ip_with_prefix.prefixlen,
+                                'gw': conf['gw6']})
+                else:
+                    f.write('NETMASK=%(netmask)s\n'
+                            'IPADDR=%(ip)s\n'
+                            'GATEWAY=%(gw)s\n' % {
+                                'ip': ip,
+                                'netmask': str(ip_with_prefix.netmask),
+                                'gw': conf['gw4']})
+        subprocess.call(('/sbin/ifup', ifname))
diff --git a/freebsd/posixvirtio.py b/freebsd/posixvirtio.py
new file mode 100644
index 0000000..1a5534a
--- /dev/null
+++ b/freebsd/posixvirtio.py
@@ -0,0 +1,60 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Virtio-Serial Port Protocol
+"""
+
+# system imports
+import os
+
+# dependent on pyserial ( http://pyserial.sf.net/ )
+# only tested w/ 1.18 (5 Dec 2002)
+
+# twisted imports
+from twisted.internet import abstract, fdesc
+
+
+class SerialPort(abstract.FileDescriptor):
+    """
+    A select()able serial device, acting as a transport.
+    """
+
+    connected = 1
+
+    def __init__(self, protocol, deviceNameOrPortNumber, reactor):
+        abstract.FileDescriptor.__init__(self, reactor)
+        self.port = deviceNameOrPortNumber
+        self._serial = os.open(
+            self.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
+        self.reactor = reactor
+        self.protocol = protocol
+        self.protocol.makeConnection(self)
+        self.startReading()
+
+    def fileno(self):
+        return self._serial
+
+    def writeSomeData(self, data):
+        """
+        Write some data to the serial device.
+        """
+        return fdesc.writeToFD(self.fileno(), data)
+
+    def doRead(self):
+        """
+        Some data's readable from serial device.
+        """
+        return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived)
+
+    def connectionLost(self, reason):
+        """
+        Called when the serial port disconnects.
+
+        Will call C{connectionLost} on the protocol that is handling the
+        serial data.
+        """
+        abstract.FileDescriptor.connectionLost(self, reason)
+        os.close(self._serial)
+        self.protocol.connectionLost(reason)
diff --git a/freebsd/ssh.py b/freebsd/ssh.py
new file mode 100644
index 0000000..b3c91ce
--- /dev/null
+++ b/freebsd/ssh.py
@@ -0,0 +1,106 @@
+from base64 import decodestring
+from struct import unpack
+import binascii
+
+
+class InvalidKeyType(Exception):
+    pass
+
+
+class InvalidKey(Exception):
+    pass
+
+
+class PubKey(object):
+
+    key_types = ('ssh-rsa', 'ssh-dsa', 'ssh-ecdsa')
+
+    # http://stackoverflow.com/questions/2494450/ssh-rsa-public-key-
+    # validation-using-a-regular-expression
+    @classmethod
+    def validate_key(cls, key_type, key):
+        try:
+            data = decodestring(key)
+        except binascii.Error:
+            raise InvalidKey()
+        int_len = 4
+        str_len = unpack('>I', data[:int_len])[0]
+        if data[int_len:int_len + str_len] != key_type:
+            raise InvalidKey()
+
+    def __init__(self, key_type, key, comment):
+        if key_type not in self.key_types:
+            raise InvalidKeyType()
+        self.key_type = key_type
+
+        PubKey.validate_key(key_type, key)
+        self.key = key
+
+        self.comment = unicode(comment)
+
+    def __hash__(self):
+        return hash(frozenset(self.__dict__.items()))
+
+    def __eq__(self, other):
+        return self.__dict__ == other.__dict__
+
+    @classmethod
+    def from_str(cls, line):
+        key_type, key, comment = line.split()
+        return PubKey(key_type, key, comment)
+
+    def __unicode__(self):
+        return u' '.join((self.key_type, self.key, self.comment))
+
+    def __repr__(self):
+        return u'<PubKey: %s>' % unicode(self)
+
+
+import unittest
+
+
+class SshTestCase(unittest.TestCase):
+    def setUp(self):
+        self.p1 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
+        self.p2 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
+        self.p3 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EC comment')
+
+    def test_invalid_key_type(self):
+        self.assertRaises(InvalidKeyType, PubKey, 'ssh-inv', 'x', 'comment')
+
+    def test_valid_key(self):
+        PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
+
+    def test_invalid_key(self):
+        self.assertRaises(InvalidKey, PubKey, 'ssh-rsa', 'x', 'comment')
+
+    def test_invalid_key2(self):
+        self.assertRaises(InvalidKey, PubKey, 'ssh-rsa',
+                          'AAAAB3MzaC1yc2EA', 'comment')
+
+    def test_repr(self):
+        p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
+        self.assertEqual(
+            repr(p), '<PubKey: ssh-rsa AAAAB3NzaC1yc2EA comment>')
+
+    def test_unicode(self):
+        p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
+        self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
+
+    def test_from_str(self):
+        p = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
+        self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
+
+    def test_eq(self):
+        self.assertEqual(self.p1, self.p2)
+        self.assertNotEqual(self.p1, self.p3)
+
+    def test_hash(self):
+        s = set()
+        s.add(self.p1)
+        s.add(self.p2)
+        s.add(self.p3)
+        self.assertEqual(len(s), 2)
+
+if __name__ == '__main__':
+    unittest.main()