diff --git a/bin/isimage b/bin/isimage
index be2ceae676c4a4075e1495915de9ac0f18ecd9da..ef829a5df78d8c8ffab2496a319a7ac24703015a 100755
--- a/bin/isimage
+++ b/bin/isimage
@@ -9,11 +9,10 @@ InstallSystems Image Manipulation Tool
 import os
 import time
 import datetime
+import argparse
 import installsystems
 from installsystems.printer import *
 from installsystems.image import SourceImage
-# The following import can be removed when min version will be python 2.7
-import installsystems.argparse as argparse
 
 class DebugAction(argparse.Action):
     '''Set installsystems in debug mode. Argparse callback'''
diff --git a/bin/isinstall b/bin/isinstall
index dde932b98e0e9a4eb8447c15acd5e030c071b541..b7008f35f56585e35d4376280d410a059cbac0bd 100755
--- a/bin/isinstall
+++ b/bin/isinstall
@@ -9,14 +9,14 @@ InstallSystems Installation Tool
 import os
 import time
 import datetime
+import argparse
 import installsystems
 import installsystems.tools as istools
 from installsystems.printer import *
-from installsystems.repository import RepositoryCache
+from installsystems.repository import RepositoryManager
 from installsystems.image import PackageImage
 from installsystems.config import ConfigFile
-# The following import can be removed when min version will be python 2.7
-import installsystems.argparse as argparse
+
 
 class DebugAction(argparse.Action):
     '''Set installsystems in debug mode. Argparse callback'''
@@ -38,7 +38,7 @@ p_main.add_argument("-c", "--config", dest="config", type=str, default=None,
                     help="config file path")
 p_main.add_argument("-v", "--image-version", dest="image_version", type=int, default=None,
                     help="image version")
-p_main.add_argument("-t", "--timeout", dest="timeout", type=int, default=3,
+p_main.add_argument("-t", "--timeout", dest="timeout", type=int, default=None,
                     help="download timeout")
 p_main.add_argument("image_name", type=str,
                     help="image to install (path or name)")
@@ -53,13 +53,11 @@ try:
         pkg = PackageImage(istools.abspath(args.image_name))
     elif image_name_type == "name":
         # init repo cache object
-        repocache = RepositoryCache(config.cache, timeout=args.timeout, verbose=args.verbose)
+        repoman = RepositoryManager(timeout=args.timeout, verbose=args.verbose)
         # register repositories
-        repocache.register(config.repos)
-        # update remote info
-        repocache.update()
+        repoman.register(config.repos)
         # get image package
-        pkg = repocache.get(args.image_name, args.image_version)
+        pkg = repoman.get(args.image_name, args.image_version)
     else:
         p_main.print_usage()
         exit(1)
diff --git a/bin/isrepo b/bin/isrepo
index 6abbac5c489689d7b0d2f866af6713dd8dc848aa..a4ef2b9d25b75ed9e1d5b5501db3c5434766c563 100755
--- a/bin/isrepo
+++ b/bin/isrepo
@@ -7,13 +7,12 @@ InstallSystems Repository Manipulation Tool
 '''
 
 import os
+import argparse
 import installsystems
 from installsystems.printer import *
-from installsystems.repository import Repository
+from installsystems.repository import Repository, RepositoryConfig
 from installsystems.image import PackageImage
 from installsystems.config import ConfigFile
-# The following import can be removed when min version will be python 2.7
-import installsystems.argparse as argparse
 
 class DebugAction(argparse.Action):
     '''Set installsystems in debug mode. Argparse callback'''
@@ -27,24 +26,24 @@ def init(args):
     '''Create an empty fresh repo tree'''
     # call init from library
     try:
-        Repository.create(args.repo, args.verbose)
+        Repository.create(args.repository, args.verbose)
     except Exception as e:
         raise Exception("init failed: %s" % e)
 
 def add(args):
     '''Add a package to repository'''
     try:
-        repo = Repository(args.repo, args.verbose)
+        repo = Repository(args.repository, args.verbose)
         pkg = PackageImage(args.path, args.verbose)
-        pkg.check_md5()
         repo.add(pkg)
     except Exception as e:
+        raise
         raise Exception("add failed: %s" % e)
 
 def delete(args):
     '''Remove a package from repository'''
     try:
-        repo = Repository(args.repo, args.verbose)
+        repo = Repository(args.repository, args.verbose)
         repo.delete(args.image_name, args.image_version)
     except Exception as e:
         raise Exception("del failed: %s" % e)
@@ -60,24 +59,20 @@ p_main.add_argument('-q', "--quiet", action="store_false", dest="verbose", defau
 p_main.add_argument("-c", "--config", dest="config", type=str, default=None,
                     help="config file path")
 p_main.add_argument("-r", "--repo-name", dest="repo_name", type=str, default=None,
-                    help="repository name")
+                    help="repository name in config file")
 subparsers = p_main.add_subparsers()
 # Init command parser
 p_init = subparsers.add_parser("init", help=init.__doc__.lower())
-p_init.add_argument("repo_image", nargs="?", default=argparse.SUPPRESS,
-                    help="Path of the new image directory")
-p_init.add_argument("repo_data", nargs="?", default=argparse.SUPPRESS,
-                    help="Path of the new data directory")
 p_init.set_defaults(func=init)
 # Add command parser
 p_add =  subparsers.add_parser("add", help=add.__doc__.lower())
 p_add.add_argument("path", type=str)
 p_add.set_defaults(func=add)
 # Del command parser
-p_del =  subparsers.add_parser("del", help=delete.__doc__.lower())
-p_del.add_argument("image_name", type=str)
-p_del.add_argument("image_version", type=str)
-p_del.set_defaults(func=delete)
+#p_del =  subparsers.add_parser("del", help=delete.__doc__.lower())
+#p_del.add_argument("image_name", type=str)
+#p_del.add_argument("image_version", type=str)
+#p_del.set_defaults(func=delete)
 try:
     # Parse and run
     args = p_main.parse_args()
@@ -89,13 +84,12 @@ try:
     else:
         repos = config.repos
     if len(repos) == 1:
-        args.repo = repos[0]
+        args.repository = repos[0]
     elif len(repos) > 1:
         raise Exception("Please select a repository with -r")
-    else:
+    if len(repos) == 0 or not isinstance(args.repository, RepositoryConfig):
         raise Exception("No image repository found")
-    debug("Image repo: %s" % args.repo.image)
-    debug("Data repo: %s" % args.repo.data)
+    debug("Repository: %s" % args.repository.path)
     args.func(args)
 except Exception as e:
     p_main.print_usage()
diff --git a/installsystems/config.py b/installsystems/config.py
index 5110f95c2e67d15fd2873696494239de6a6a3735..d32a01bcd922a853aaa64e7cd3e7c11646260d50 100644
--- a/installsystems/config.py
+++ b/installsystems/config.py
@@ -46,7 +46,7 @@ class ConfigFile(object):
                 # each section is a repository
                 for rep in cp.sections():
                     # check if its a repo section
-                    if "image" not in cp.options(rep):
+                    if "path" not in cp.options(rep):
                         continue
                     # get all options in repo
                     self._repos.append(RepositoryConfig(rep, **dict(cp.items(rep))))
diff --git a/installsystems/database.py b/installsystems/database.py
index b5dd8463d9139105d8f14e4290139d7e49eb8375..ec5c158746c9810e2fcd826b5b052925553a04e4 100644
--- a/installsystems/database.py
+++ b/installsystems/database.py
@@ -10,6 +10,7 @@ import json
 import os
 import shutil
 import tarfile
+import cStringIO
 import installsystems.tools as istools
 from installsystems.tarball import Tarball
 from installsystems.printer import *
@@ -22,9 +23,12 @@ class Database(object):
     @classmethod
     def create(cls, path, verbose=True):
         arrow("Creating repository database", 1, verbose)
+        # check locality
+        if istools.pathtype(path) != "file":
+            raise NotImplementedError("Database creation must be local")
         dbpath = istools.abspath(path)
         if os.path.exists(dbpath):
-            raise Exception("db already exists")
+            raise Exception("Database already exists. Remove it before")
         try:
             tarball = Tarball.open(dbpath, mode="w:gz", dereference=True)
             tarball.add_str("format", Database.db_format, tarfile.REGTYPE, 0444)
@@ -36,39 +40,67 @@ class Database(object):
     def __init__(self, path, verbose=True):
         self.path = istools.abspath(path)
         self.verbose = verbose
+        # load db in memory
+        self.file = cStringIO.StringIO()
+        shutil.copyfileobj(istools.uopen(self.path), self.file)
+
+    def get(self, name, version):
+        '''Return a description dict from a package name'''
+        # parse tarball
+        try:
+            self.file.seek(0)
+            tarball = Tarball.open(fileobj=self.file, mode="r:gz")
+            rdata = tarball.get_str("%s-%s" % (name, version))
+            tarball.close()
+        except KeyError:
+            raise Exception("No package %s version %s in metadata" % (name, version))
+        except Exception as e:
+            raise Exception("Unable to read db %s version %s: e" % (name, version, e))
+        # convert loaded data into dict (json parser)
+        try:
+            return json.loads(rdata)
+        except Exception as e:
+            raise Exception("Invalid metadata in package %s version %s: e" % (name, version, e))
 
     def add(self, package):
         '''Add a packaged image to a db'''
         arrow("Adding metadata to db", 1, self.verbose)
+        # check locality
+        if istools.pathtype(self.path) != "file":
+            raise NotImplementedError("Database addition must be local")
         # naming
-        name = "%s.json" % package.name
         newdb_path = "%s.new" % self.path
         # compute md5
-        arrow("Compute MD5 of %s" % os.path.relpath(package.path), 2, self.verbose)
-        md5 = package.md5
         arrow("Formating metadata", 2, self.verbose)
         desc = package.description
-        desc["md5"] = md5
+        desc["md5"] = package.md5
         jdesc = json.dumps(desc)
         try:
-            arrow("Adding to tarball", 2, self.verbose)
-            db = Tarball.open(self.path, mode='r:gz')
-            newdb = Tarball.open(newdb_path, mode='w:gz')
+            arrow("Adding metadata", 2, self.verbose)
+            self.file.seek(0)
+            newfile = cStringIO.StringIO()
+            db = Tarball.open(fileobj=self.file, mode='r:gz')
+            newdb = Tarball.open(fileobj=newfile, mode='w:gz')
             for ti in db.getmembers():
-                if ti.name != name:
+                if ti.name != package.id:
                     newdb.addfile(ti, db.extractfile(ti))
-            newdb.add_str(name, jdesc, tarfile.REGTYPE, 0444)
+            newdb.add_str(package.id, jdesc, tarfile.REGTYPE, 0644)
             db.close()
             newdb.close()
-            # preserve permission and stats when moving
-            shutil.copystat(self.path, newdb_path)
-            os.rename(newdb_path, self.path)
+            # writing to disk
+            arrow("Writing to disk", 2, self.verbose)
+            self.file.close()
+            self.file = newfile
+            self.write()
         except Exception as e:
             raise Exception("Adding metadata fail: %s" % e)
 
     def delete(self, name, version):
         '''Deltete a packaged image'''
         arrow("Removing metadata from db", 1, self.verbose)
+        # check locality
+        if istools.pathtype(self.path) != "file":
+            raise NotImplementedError("Database deletion must be local")
         newdb_path = "%s.new" % self.path
         fname = "%s-%s.json" % (name, version)
         try:
@@ -88,19 +120,21 @@ class Database(object):
     def databalls(self, name, version):
         '''List data tarballs filenames'''
         try:
-            db = Tarball.open(self.path, mode='r:gz')
+            self.file.seek(0)
+            db = Tarball.open(fileobj=self.file, mode='r:gz')
             jdesc = json.loads(db.get_str("%s-%s.json" % (name, version)))
             db.close()
             return jdesc["data"]
         except Exception as e:
-            raise Exception("Listing data tarballs fail: %s" % e)
+            raise Exception("List data tarballs fail: %s" % e)
 
     def find(self, name, version=None):
         '''Find last version of an image'''
         try:
-            tarb = Tarball.open(self.path, mode='r:gz')
+            self.file.seek(0)
+            tarb = Tarball.open(fileobj=self.file, mode='r:gz')
             candidates = [ int((os.path.splitext(tpname)[0]).rsplit("-", 1)[1])
-                           for tpname in tarb.getnames("%s-\d+.json" % name) ]
+                           for tpname in tarb.getnames("%s-\d+" % name) ]
             tarb.close()
         except Exception as e:
             raise Exception("Find in db %s fail: %s" % (self.path, e))
@@ -113,4 +147,15 @@ class Database(object):
         # check if version exists
         if int(version) not in candidates:
             return None
-        return (name, version)
+        return self.get(name, version)
+
+    def write(self):
+        '''Write current dabatase into its file'''
+        if istools.pathtype(self.path) != "file":
+            raise NotImplementedError("Database writing must be local")
+        try:
+            dest = open(self.path, "w")
+            self.file.seek(0)
+            shutil.copyfileobj(self.file, dest)
+        except Exception as e:
+            raise Exception("Unable to write database: %s" % e)
diff --git a/installsystems/image.py b/installsystems/image.py
index fac0400adb1a4d8ed3a85d65caf1022d7a5fbc4a..23063875f996a06da959e889662771ecca5da26d 100644
--- a/installsystems/image.py
+++ b/installsystems/image.py
@@ -10,11 +10,12 @@ import os
 import stat
 import time
 import json
-import StringIO
 import ConfigParser
 import subprocess
 import tarfile
 import re
+import cStringIO
+import shutil
 import installsystems.template as istemplate
 import installsystems.tools as istools
 from installsystems.printer import *
@@ -43,6 +44,10 @@ class SourceImage(Image):
     @classmethod
     def create(cls, path, verbose=True):
         '''Create an empty source image'''
+        # check local repository
+        if istools.pathtype(path) != "file":
+            raise NotImplementedError("SourceImage must be local")
+        # main path
         parser_path = os.path.join(path, "parser")
         setup_path = os.path.join(path, "setup")
         data_path = os.path.join(path, "data")
@@ -82,16 +87,19 @@ class SourceImage(Image):
         return cls(path, verbose)
 
     def __init__(self, path, verbose=True):
+        # check local repository
+        if istools.pathtype(path) != "file":
+            raise NotImplementedError("SourceImage must be local")
         Image.__init__(self)
         self.base_path = path
         self.parser_path = os.path.join(path, "parser")
         self.setup_path = os.path.join(path, "setup")
         self.data_path = os.path.join(path, "data")
         self.verbose = verbose
-        self.valid_source_image()
+        self.validate_source_image()
         self.description = self.parse_description()
 
-    def valid_source_image(self):
+    def validate_source_image(self):
         '''Check if we are a valid SourceImage'''
         for d in (self.base_path, self.parser_path, self.setup_path, self.data_path):
             if not os.path.exists(d):
@@ -112,7 +120,7 @@ class SourceImage(Image):
         if os.path.exists(tarpath) and overwrite == False:
             raise Exception("Tarball already exists. Remove it before")
         #  Create data tarballs
-        data_d = self.create_data_tarballs()
+        self.create_data_tarballs()
         # generate description.json
         jdesc = self.generate_json_description()
         # creating scripts tarball
@@ -139,6 +147,7 @@ class SourceImage(Image):
         # closing tarball file
         tarball.close()
 
+    @property
     def data_tarballs(self):
         '''List all data tarballs in data directory'''
         databalls = dict()
@@ -147,36 +156,41 @@ class SourceImage(Image):
                                        self.description["version"],
                                        dname,
                                        self.extension_data)
-            databalls[filename] = os.path.abspath(os.path.join(self.data_path, dname))
+            databalls[dname] = filename
         return databalls
 
     def create_data_tarballs(self):
-        '''Create all data tarballs in data directory'''
+        '''
+        Create all data tarballs in data directory
+        Doen't compute md5 during creation because tarball can
+        be created manually
+        '''
         arrow("Creating data tarballs", 1, self.verbose)
         # build list of data tarball candidate
-        candidates = self.data_tarballs()
+        candidates = self.data_tarballs
         if len(candidates) == 0:
             arrow("No data tarball", 2, self.verbose)
             return
         # create tarballs
-        for candidate in candidates:
-            path = os.path.join(self.base_path, candidate)
-            if os.path.exists(path):
-                arrow("Tarball %s already exists." % candidate, 2, self.verbose)
+        for (dn, df) in candidates.items():
+            source_path = os.path.join(self.data_path, dn)
+            dest_path = os.path.join(self.base_path, df)
+            if os.path.exists(dest_path):
+                arrow("Tarball %s already exists." % df, 2, self.verbose)
             else:
-                arrow("Creating tarball %s" % candidate, 2, self.verbose)
-                self.create_data_tarball(path, candidates[candidate])
+                arrow("Creating tarball %s" % df, 2, self.verbose)
+                self.create_data_tarball(dest_path, source_path)
 
     def create_data_tarball(self, tar_path, data_path):
         '''Create a data tarball'''
+        # compute dname to set as a base directory
         dname = os.path.basename(data_path)
         # not derefence for directory. Verbatim copy.
         ddref = False if os.path.isdir(data_path) else True
         try:
-            # opening file
+            # Tarballing
             tarball = Tarball.open(tar_path, "w:gz", dereference=ddref)
-            tarball.add(data_path, arcname=dname, recursive=True)
-            # closing tarball file
+            tarball.add(data_path, arcname="/", recursive=True)
             tarball.close()
         except Exception as e:
             raise Exception("Unable to create data tarball %s: %s" % (tar_path, e))
@@ -191,7 +205,7 @@ class SourceImage(Image):
         return tinfo
 
     def generate_json_description(self):
-        '''Generate a json description file'''
+        '''Generate a JSON description file'''
         arrow("Generating JSON description", 1, self.verbose)
         # copy description
         desc = self.description.copy()
@@ -200,13 +214,11 @@ class SourceImage(Image):
         desc["date"] = int(time.time())
         # append data tarballs info
         desc["data"] = dict()
-        for dt in self.data_tarballs():
-            arrow("Compute MD5 of %s" % dt, 2, self.verbose)
-            path = os.path.join(self.base_path, dt)
-            desc["data"][dt] = { "size": os.path.getsize(path),
-                                 "md5": istools.md5sum(path) }
-        # create file
-        filedesc = StringIO.StringIO()
+        for (dn, df) in self.data_tarballs.items():
+            arrow("Compute MD5 of %s" % df, 2, self.verbose)
+            tb_path = os.path.join(self.base_path, df)
+            desc["data"][dn] = { "size": os.path.getsize(tb_path),
+                                 "md5": istools.md5sum(tb_path) }
         # serialize
         return json.dumps(desc)
 
@@ -221,24 +233,36 @@ class SourceImage(Image):
             for n in ("name","version", "description", "author"):
                 d[n] = cp.get("image", n)
         except Exception as e:
-            raise Exception("description: %s" % e)
+            raise Exception("Invalid description: %s" % e)
         return d
 
+
 class PackageImage(Image):
     '''Packaged image manipulation class'''
 
     def __init__(self, path, verbose=True):
         Image.__init__(self)
-        self.path = os.path.abspath(path)
+        self.path = istools.abspath(path)
         self.base_path = os.path.dirname(self.path)
         self.verbose = verbose
-        self.tarball = Tarball.open(self.path, mode='r:gz')
+        # load image in memory
+        arrow("Loading tarball in memory", 1, verbose)
+        self.file = cStringIO.StringIO()
+        fo = istools.uopen(self.path)
+        shutil.copyfileobj(fo, self.file)
+        fo.close()
+        # set tarball fo
+        self.file.seek(0)
+        self.tarball = Tarball.open(fileobj=self.file, mode='r:gz')
         self.parse()
+        self._md5 = None
 
     @property
     def md5(self):
         '''Return md5sum of the current tarball'''
-        return istools.md5sum(self.path)
+        if self._md5 is None:
+            self._md5 = istools.md5sum(self.path)
+        return self._md5
 
     @property
     def id(self):
@@ -263,7 +287,12 @@ class PackageImage(Image):
     @property
     def datas(self):
         '''Create a dict of data tarballs'''
-        return dict(self.description["data"])
+        d = dict()
+        for dt in self.description["data"]:
+            d[dt] = dict(self.description["data"][dt])
+            d[dt]["filename"] = "%s-%s%s" % (self.id, dt, self.extension_data)
+            d[dt]["path"] = os.path.join(self.base_path, d[dt]["filename"])
+        return d
 
     def parse(self):
         '''Parse tarball and extract metadata'''
@@ -311,10 +340,56 @@ class PackageImage(Image):
         # order matter!
         l_scripts.sort()
         # run scripts
-        try:
-            for n_scripts in l_scripts:
-                arrow(os.path.basename(n_scripts), 2, self.verbose)
+        for n_scripts in l_scripts:
+            arrow(os.path.basename(n_scripts), 2, self.verbose)
+            try:
                 s_scripts = self.tarball.get_str(n_scripts)
+            except Exception as e:
+                raise Exception("Extracting script %s fail: %s" %
+                                (os.path.basename(n_scripts), e))
+            try:
                 exec(s_scripts, gl, dict())
+            except Exception as e:
+                raise
+                raise Exception("Execution script %s fail: %s" %
+                                (os.path.basename(n_scripts), e))
+
+    def extractdata(self, dataname, target, filelist=None):
+        '''Extract a data tarball into target'''
+        # check if dataname exists
+        if dataname not in self.description["data"].keys():
+            raise Exception("No such data")
+        # tarball info
+        tinfo = self.description["data"][dataname]
+        # build data tar paths
+        paths = [ os.path.join(self.base_path, tinfo["md5"]),
+                  os.path.join(self.base_path, "%s-%s%s" % (self.id,
+                                                            dataname,
+                                                            self.extension_data)) ]
+        # try to open path
+        fo = None
+        for path in paths:
+            try:
+                fo = istools.uopen(path)
+                break
+            except Exception:
+                pass
+        # error if no file is openned
+        if fo is None:
+            raise Exception("Unable to open data tarball")
+        try:
+            # create tar object
+            t = Tarball.open(fileobj=fo, mode="r|gz")
+        except Exception as e:
+            raise Exception("Invalid data tarball: %s" % e)
+        # filter on file to extact
+        if filelist is not None:
+            members = []
+            for fi in filelist:
+                members += t.gettarinfo(name)
+        else:
+            members = None
+        try:
+            t.extractall(target, members)
         except Exception as e:
-            raise Exception("%s fail: %s" % (os.path.basename(n_scripts), e))
+            raise Exception("Extracting failed: %s" % e)
diff --git a/installsystems/repository.py b/installsystems/repository.py
index a1bcf618316625efb302acb9dad22e3d8f8062c3..6113d2b311370380f0a8206b39f37362dbf07369 100644
--- a/installsystems/repository.py
+++ b/installsystems/repository.py
@@ -25,26 +25,27 @@ class Repository(object):
     def __init__(self, config, verbose=True):
         self.verbose = verbose
         self.config = config
-        self.db = Database(os.path.join(config.image, config.db), verbose=self.verbose)
-        self.last_path = os.path.join(config.image, config.last)
+        self.db = Database(os.path.join(config.path, config.dbname), verbose=self.verbose)
 
     @classmethod
     def create(cls, config, verbose=True):
         '''Create an empty base repository'''
+        # check local repository
+        if istools.pathtype(config.path) != "file":
+            raise NotImplementedError("Repository creation must be local")
         # create base directories
         arrow("Creating base directories", 1, verbose)
-        for d in (config.image, config.data):
-            try:
-                if os.path.exists(d):
-                    arrow("%s already exists" % d, 2, verbose)
-                else:
-                    istools.mkdir(d, config.chown, config.chgroup, config.dchmod)
-                    arrow("%s directory created" % d, 2, verbose)
-            except Exception as e:
-                raise
-                raise Exception("Unable to create directory %s: %s" % (d, e))
+        # creating local directory
+        try:
+            if os.path.exists(config.path):
+                arrow("%s already exists" % config.path, 2, verbose)
+            else:
+                istools.mkdir(config.path, config.chown, config.chgroup, config.dchmod)
+                arrow("%s directory created" % config.path, 2, verbose)
+        except Exception as e:
+            raise Exception("Unable to create directory %s: %s" % (config.path, e))
         # create database
-        dbpath = os.path.join(config.image, "db")
+        dbpath = os.path.join(config.path, config.dbname)
         d = Database.create(dbpath, verbose=verbose)
         istools.chrights(dbpath, config.chown, config.chgroup, config.fchmod)
         # create last file
@@ -55,46 +56,79 @@ class Repository(object):
 
     def update_last(self):
         '''Update last file to current time'''
+        # check local repository
+        if istools.pathtype(self.config.path) != "file":
+            raise NotImplementedError("Repository addition must be local")
         try:
-            open(self.last_path, "w").write("%s\n" % int(time.time()))
-            os.chown(self.last_path, self.config.chown, self.config.chgroup)
-            os.chmod(self.last_path, self.config.fchmod)
+            arrow("Updating last file", 1, self.verbose)
+            last_path = os.path.join(self.config.path, self.config.lastname)
+            open(last_path, "w").write("%s\n" % int(time.time()))
+            os.chown(last_path, self.config.chown, self.config.chgroup)
+            os.chmod(last_path, self.config.fchmod)
         except Exception as e:
             raise Exception("Update last file failed: %s" % e)
 
     def last(self):
         '''Return the last value'''
         try:
-            return int(open(self.last_path, "r").read().rstrip())
+            last_path = os.path.join(config.path, config.lastname)
+            return int(istools.uopen(last_path, "r").read().rstrip())
         except Exception as e:
             raise Exception("Read last file failed: %s" % e)
         return 0
 
     def add(self, package):
         '''Add a packaged image to repository'''
+        # check local repository
+        if istools.pathtype(self.config.path) != "file":
+            raise NotImplementedError("Repository addition must be local")
         # copy file to directory
-        arrow("Adding file to directories", 1, self.verbose)
-        arrow("Adding %s" % os.path.relpath(package.path), 2, self.verbose)
-        istools.copy(package.path, self.config.image,
-                     self.config.chown, self.config.chgroup, self.config.fchmod)
-        for db in package.databalls:
-            arrow("Adding %s" % os.path.basename(db), 2, self.verbose)
-            istools.copy(db, self.config.data,
-                         self.config.chown, self.config.chgroup, self.config.fchmod)
-        # add file to db
+        arrow("MD5summing tarballs", 1, self.verbose)
+        # build dict of file to add
+        filelist = dict()
+        # script tarball
+        arrow(package.filename, 2, self.verbose)
+        filelist[package.md5] = package.path
+        # data tarballs
+        datas = package.datas
+        for dt in datas:
+            dt_path = datas[dt]["path"]
+            old_md5 = datas[dt]["md5"]
+            arrow(os.path.relpath(dt_path), 2, self.verbose)
+            md5 = istools.md5sum(dt_path)
+            if md5 != old_md5:
+                raise Exception("MD5 mismatch on %s" % dt_path)
+            filelist[md5] = dt_path
+        # adding file to repository
+        arrow("Adding files to directory", 1, self.verbose)
+        for md5 in filelist:
+            dest = os.path.join(self.config.path, md5)
+            source = filelist[md5]
+            if os.path.exists(dest):
+                arrow("Skipping %s: already exists" % (os.path.basename(source)),
+                      2, self.verbose)
+            else:
+                arrow("Adding %s (%s)" % (os.path.basename(source), md5), 2, self.verbose)
+                istools.copy(source, dest,
+                             self.config.chown, self.config.chgroup, self.config.fchmod)
+        # add description to db
         self.db.add(package)
         # update last file
-        arrow("Updating last file", 1, self.verbose)
         self.update_last()
 
     def delete(self, name, version):
         '''Delete an image from repository'''
-        if self.db.find(name, version) is None:
+        raise NotImplementedError()
+        # check local repository
+        if istools.pathtype(self.config.path) != "file":
+            raise NotImplementedError("Repository deletion must be local")
+        desc = self.db.find(name, version)
+        if desc is None:
             error("Unable to find %s version %s in database" % (name, version))
         # removing script tarballs
         arrow("Removing script tarball", 1, self.verbose)
-        tpath = os.path.join(self.config.image,
-                             "%s-%s%s" % (name, version, Image.image_extension))
+        tpath = os.path.join(self.config.path,
+                             "%s-%s%s" % (name, version, Image.extension))
         if os.path.exists(tpath):
             os.unlink(tpath)
             arrow("%s removed" % os.path.basename(tpath), 2, self.verbose)
@@ -111,6 +145,10 @@ class Repository(object):
         arrow("Updating last file", 1, self.verbose)
         self.update_last()
 
+    def get(self, name, version):
+        '''return a package from a name and version of pakage'''
+        desc = self.db.get(name, version)
+        return PackageImage(os.path.join(self.config.path, desc["md5"]), verbose=self.verbose)
 
 class RepositoryConfig(object):
     '''Repository configuration container'''
@@ -118,10 +156,9 @@ class RepositoryConfig(object):
     def __init__(self, *args, **kwargs):
         # set default value for arguments
         self.name = args[0]
-        self.db = "db"
-        self.last = "last"
-        self.image = ""
-        self.data = ""
+        self.dbname = "db"
+        self.lastname = "last"
+        self.path = ""
         self.chown = os.getuid()
         self.chgroup = os.getgid()
         umask = os.umask(0)
@@ -131,8 +168,7 @@ class RepositoryConfig(object):
         self.update(**kwargs)
 
     def update(self, *args, **kwargs):
-        '''
-        Update attribute with checking value
+        '''Update attribute with checking value
         All attribute must already exists
         '''
         # autoset parameter in cmdline
@@ -172,6 +208,48 @@ class RepositoryConfig(object):
     def __contains__(self, key):
         return key in self.__dict__
 
+class RepositoryManager(object):
+    '''Manage multiple repostories'''
+
+    def __init__(self, timeout=None, verbose=True):
+        self.verbose = verbose
+        self.timeout = 3 if timeout is None else timeout
+        self.repos = {}
+
+    def register(self, configs):
+        '''Register a list of repository from its config'''
+        for conf in configs:
+            self.repos[conf.name] = Repository(conf, self.verbose)
+
+    def find_image(self, name, version):
+        '''Find a repository containing image'''
+        if version is None:
+            arrow("Serching last version of %s" % name, 1, self.verbose)
+        else:
+            arrow("Serching %s version %s " % (name, version), 1, self.verbose)
+        img = None
+        # search in all repositories
+        desc = None
+        for repo in self.repos:
+            desc = self.repos[repo].db.find(name, version)
+            if desc is not None:
+                # \o/
+                break
+        if desc is None:
+            arrow("Not found", 2, self.verbose)
+            if version is None:
+                error("Unable to find a version of image %s" % name)
+            else:
+                error("Unable to find image %s version %s" % (name, version))
+        arrow("Found %s version %s " % (desc["name"], desc["version"]), 2, self.verbose)
+        return (desc, self.repos[repo])
+
+    def get(self, name, version=None):
+        '''Return a package object from local cache'''
+        # find an image name/version in repository
+        (desc, repo) = self.find_image(name, version)
+        # get pkg object
+        return repo.get(desc["name"], desc["version"])
 
 class RepositoryCache(object):
     '''Local repository cache class'''
diff --git a/installsystems/template.py b/installsystems/template.py
index cbcd614761bbba99cd97825d817a15e5fad5b284..61f57aaa3d44c93516404e0ce83cae22fe906215 100644
--- a/installsystems/template.py
+++ b/installsystems/template.py
@@ -13,6 +13,8 @@ parser = """# -*- python -*-
 # -*- coding: utf-8 -*-
 
 parser.add_argument("-n", "--hostname", dest="hostname", type=str, required=True)
+parser.add_argument("target", type=str,
+  help="target installation directory")
 
 # vim:set ts=2 sw=2 noet:
 """
@@ -22,5 +24,7 @@ setup = """# -*- python -*-
 
 print "hostname: %s" % args.hostname
 
+image.extractdata("rootfs", args.target)
+
 # vim:set ts=2 sw=2 noet:
 """
diff --git a/installsystems/tools.py b/installsystems/tools.py
index 56c086757c21751499261f3b7b4ffa82fafaa0e0..504f048a02735bf2213a9c1bba3d079068fd103b 100644
--- a/installsystems/tools.py
+++ b/installsystems/tools.py
@@ -81,8 +81,10 @@ def abspath(path):
     else:
         return None
 
-def ropen(path):
-    '''Open a file which can be remote'''
+def uopen(path):
+    '''Universal Open
+    Create a file-like object to a file which can be remote
+    '''
     ftype = pathtype(path)
     if ftype == "file":
         return open(path, "r")