diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2c6e521175420f24fa4e8772cf7d74c986f8c224..27cb2d651a9572e37609ea6e49bb2659bb618532 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -47,7 +47,7 @@ pylint:
   before_script:
     - pip install pylint
   script:
-    - pylint $PWD/pyotb
+    - pylint $PWD/pyotb --disable=fixme
 
 codespell:
   extends: .static_analysis
@@ -84,7 +84,7 @@ test_install:
   before_script:
     - pip install pytest pytest-cov
 
-test_module_core:
+module_core:
   extends: .tests
   variables:
     OTB_LOGGER_LEVEL: INFO
@@ -101,7 +101,7 @@ test_module_core:
     - curl -fsLI $PLEIADES_IMG_URL
     - python3 -m pytest -vv --junitxml=test-module-core.xml --cov-report xml:coverage.xml tests/test_core.py
 
-test_pipeline_permutations:
+pipeline_permutations:
   extends: .tests
   variables:
     OTB_LOGGER_LEVEL: WARNING
diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index bf553928bd558a37600a3d6bb50796dcc64e0fda..aff3736392ba7e0d464110dccbba05063b854b38 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -1,10 +1,19 @@
 # -*- coding: utf-8 -*-
 """This module provides convenient python wrapping of otbApplications."""
-__version__ = "2.0.0.dev4"
+__version__ = "2.0.0.dev6"
 
+from .install import install_otb
 from .helpers import logger, set_logger_level
+from .core import (
+    OTBObject,
+    App,
+    Input,
+    Output,
+    get_nbchannels,
+    get_pixel_type,
+    summarize,
+)
 from .apps import *
-from .core import App, Input, Output, get_nbchannels, get_pixel_type, summarize, OTBObject
 
 from .functions import (  # pylint: disable=redefined-builtin
     all,
diff --git a/pyotb/helpers.py b/pyotb/helpers.py
index 5363937940485b33f74c31ef8371af46279e2e56..3fc93b389fde634b0e81f24ae20a81fa74eb8776 100644
--- a/pyotb/helpers.py
+++ b/pyotb/helpers.py
@@ -1,13 +1,17 @@
 # -*- coding: utf-8 -*-
-"""This module helps to ensure we properly initialize pyotb: only in case OTB is found and apps are available."""
+"""This module ensure we properly initialize pyotb, or raise SystemExit in case of broken install."""
 import logging
 import os
 import sys
+import sysconfig
 from pathlib import Path
 from shutil import which
 
+from .install import install_otb, interactive_config
+
 # Allow user to switch between OTB directories without setting every env variable
 OTB_ROOT = os.environ.get("OTB_ROOT")
+DOCS_URL = "https://www.orfeo-toolbox.org/CookBook/Installation.html"
 
 # Logging
 # User can also get logger with `logging.getLogger("pyOTB")`
@@ -39,19 +43,20 @@ def set_logger_level(level: str):
     logger_handler.setLevel(getattr(logging, level))
 
 
-def find_otb(prefix: str = OTB_ROOT, scan: bool = True, scan_userdir: bool = True):
-    """Try to load OTB bindings or scan system, help user in case of failure, set env variables.
+def find_otb(prefix: str = OTB_ROOT, scan: bool = True):
+    """Try to load OTB bindings or scan system, help user in case of failure, set env.
 
-    Path precedence :                                OTB_ROOT > python bindings directory
-        OR search for releases installations    :    HOME
-        OR (for Linux)                          :    /opt/otbtf > /opt/otb > /usr/local > /usr
-        OR (for MacOS)                          :    ~/Applications
-        OR (for Windows)                        :    C:/Program Files
+    If in interactive prompt, user will be asked if he wants to install OTB.
+    The OTB_ROOT variable allow one to override default OTB version, with auto env setting.
+    Path precedence : $OTB_ROOT > location of python bindings location
+    Then, if OTB is not found:
+        search for releases installations: $HOME/Applications
+        OR (for Linux): /opt/otbtf > /opt/otb > /usr/local > /usr
+        OR (for Windows): C:/Program Files
 
     Args:
         prefix: prefix to search OTB in (Default value = OTB_ROOT)
         scan: find otb in system known locations (Default value = True)
-        scan_userdir: search for OTB release in user's home directory (Default value = True)
 
     Returns:
         otbApplication module
@@ -65,14 +70,15 @@ def find_otb(prefix: str = OTB_ROOT, scan: bool = True, scan_userdir: bool = Tru
             import otbApplication as otb  # pylint: disable=import-outside-toplevel
 
             return otb
-        except EnvironmentError as e:
+        except SystemError as e:
             raise SystemExit(f"Failed to import OTB with prefix={prefix}") from e
         except ImportError as e:
             __suggest_fix_import(str(e), prefix)
             raise SystemExit("Failed to import OTB. Exiting.") from e
     # Else try import from actual Python path
     try:
-        # Here, we can't properly set env variables before OTB import. We assume user did this before running python
+        # Here, we can't properly set env variables before OTB import.
+        # We assume user did this before running python
         # For LD_LIBRARY_PATH problems, use OTB_ROOT instead of PYTHONPATH
         import otbApplication as otb  # pylint: disable=import-outside-toplevel
 
@@ -89,18 +95,25 @@ def find_otb(prefix: str = OTB_ROOT, scan: bool = True, scan_userdir: bool = Tru
             ) from e
     # Else search system
     logger.info("Failed to import OTB. Searching for it...")
-    prefix = __find_otb_root(scan_userdir)
-    # Try to import one last time before raising error
+    prefix = __find_otb_root()
+    # Try auto install if shell is interactive
+    if not prefix and hasattr(sys, "ps1"):
+        if input("OTB is missing. Do you want to install it ? (y/n): ") == "y":
+            return find_otb(install_otb(*interactive_config()))
+        raise SystemError("OTB libraries not found on disk. ")
+    if not prefix:
+        raise SystemExit(
+            "OTB libraries not found on disk. "
+            "To install it, open an interactive python shell and 'import pyotb'"
+        )
+    # If OTB was found on disk, set env and try to import one last time
     try:
         set_environment(prefix)
         import otbApplication as otb  # pylint: disable=import-outside-toplevel
 
         return otb
-    except EnvironmentError as e:
+    except SystemError as e:
         raise SystemExit("Auto setup for OTB env failed. Exiting.") from e
-    # Unknown error
-    except ModuleNotFoundError as e:
-        raise SystemExit("Can't run without OTB installed. Exiting.") from e
     # Help user to fix this
     except ImportError as e:
         __suggest_fix_import(str(e), prefix)
@@ -125,7 +138,7 @@ def set_environment(prefix: str):
     # External libraries
     lib_dir = __find_lib(prefix)
     if not lib_dir:
-        raise EnvironmentError("Can't find OTB external libraries")
+        raise SystemError("Can't find OTB external libraries")
     # This does not seems to work
     if sys.platform == "linux" and built_from_source:
         new_ld_path = f"{lib_dir}:{os.environ.get('LD_LIBRARY_PATH') or ''}"
@@ -133,17 +146,17 @@ def set_environment(prefix: str):
     # Add python bindings directory first in PYTHONPATH
     otb_api = __find_python_api(lib_dir)
     if not otb_api:
-        raise EnvironmentError("Can't find OTB Python API")
+        raise SystemError("Can't find OTB Python API")
     if otb_api not in sys.path:
         sys.path.insert(0, otb_api)
-    # Add /bin first in PATH, in order to avoid conflicts with another GDAL install when using os.system()
+    # Add /bin first in PATH, in order to avoid conflicts with another GDAL install
     os.environ["PATH"] = f"{prefix / 'bin'}{os.pathsep}{os.environ['PATH']}"
-    # Applications path  (this can be tricky since OTB import will succeed even without apps)
+    # Ensure APPLICATION_PATH is set
     apps_path = __find_apps_path(lib_dir)
     if Path(apps_path).exists():
         os.environ["OTB_APPLICATION_PATH"] = apps_path
     else:
-        raise EnvironmentError("Can't find OTB applications directory")
+        raise SystemError("Can't find OTB applications directory")
     os.environ["LC_NUMERIC"] = "C"
     os.environ["GDAL_DRIVER_PATH"] = "disable"
 
@@ -159,7 +172,7 @@ def set_environment(prefix: str):
         gdal_data = str(prefix / "share/data")
         proj_lib = str(prefix / "share/proj")
     else:
-        raise EnvironmentError(
+        raise SystemError(
             f"Can't find GDAL location with current OTB prefix '{prefix}' or in /usr"
         )
     os.environ["GDAL_DATA"] = gdal_data
@@ -171,7 +184,7 @@ def __find_lib(prefix: str = None, otb_module=None):
 
     Args:
         prefix: try with OTB root directory
-        otb_module: try with OTB python module (otbApplication) library path if found, else None
+        otb_module: try with otbApplication library path if found, else None
 
     Returns:
         lib path
@@ -235,12 +248,9 @@ def __find_apps_path(lib_dir: Path):
     return ""
 
 
-def __find_otb_root(scan_userdir: bool = False):
+def __find_otb_root():
     """Search for OTB root directory in well known locations.
 
-    Args:
-        scan_userdir: search with glob in $HOME directory
-
     Returns:
         str path of the OTB directory
 
@@ -263,78 +273,72 @@ def __find_otb_root(scan_userdir: bool = False):
                 prefix = path.parent.parent.parent
             else:
                 prefix = path.parent.parent
-            prefix = prefix.absolute()
     elif sys.platform == "win32":
-        for path in Path("c:/Program Files").glob("**/OTB-*/lib"):
-            logger.info("Found %s", path.parent)
-            prefix = path.parent.absolute()
-    elif sys.platform == "darwin":
-        for path in (Path.home() / "Applications").glob("**/OTB-*/lib"):
-            logger.info("Found %s", path.parent)
-            prefix = path.parent.absolute()
-    # If possible, use OTB found in user's HOME tree (this may take some time)
-    if scan_userdir:
-        for path in Path.home().glob("**/OTB-*/lib"):
+        for path in sorted(Path("c:/Program Files").glob("**/OTB-*/lib")):
             logger.info("Found %s", path.parent)
-            prefix = path.parent.absolute()
-    # Return latest found prefix (and version), see precedence in function def find_otb()
-    return prefix
+            prefix = path.parent
+    # Search for pyotb OTB install, or default on macOS
+    apps = Path.home() / "Applications"
+    for path in sorted(apps.glob("OTB-*/lib/")):
+        logger.info("Found %s", path.parent)
+        prefix = path.parent
+    # Return latest found prefix (and version), see precedence in find_otb() docstrings
+    if isinstance(prefix, Path):
+        return prefix.absolute()
+    return None
 
 
 def __suggest_fix_import(error_message: str, prefix: str):
     """Help user to fix the OTB installation with appropriate log messages."""
     logger.critical("An error occurred while importing OTB Python API")
     logger.critical("OTB error message was '%s'", error_message)
-    if sys.platform == "linux":
-        if error_message.startswith("libpython3."):
-            logger.critical(
-                "It seems like you need to symlink or recompile python bindings"
-            )
-            if sys.executable.startswith("/usr/bin"):
-                lib = (
-                    f"/usr/lib/x86_64-linux-gnu/libpython3.{sys.version_info.minor}.so"
-                )
-                if which("ctest"):
-                    logger.critical(
-                        "To recompile python bindings, use 'cd %s ; source otbenv.profile ; "
-                        "ctest -S share/otb/swig/build_wrapping.cmake -VV'",
-                        prefix,
-                    )
-                elif Path(lib).exists():
-                    expect_minor = int(error_message[11])
-                    if expect_minor != sys.version_info.minor:
-                        logger.critical(
-                            "Python library version mismatch (OTB was expecting 3.%s) : "
-                            "a simple symlink may not work, depending on your python version",
-                            expect_minor,
-                        )
-                    target_lib = f"{prefix}/lib/libpython3.{expect_minor}.so.rh-python3{expect_minor}-1.0"
-                    logger.critical("Use 'ln -s %s %s'", lib, target_lib)
-                else:
-                    logger.critical(
-                        "You may need to install cmake in order to recompile python bindings"
-                    )
-            else:
-                logger.critical(
-                    "Unable to automatically locate python dynamic library of %s",
-                    sys.executable,
-                )
-    elif sys.platform == "win32":
+    if sys.platform == "win32":
         if error_message.startswith("DLL load failed"):
             if sys.version_info.minor != 7:
                 logger.critical(
-                    "You need Python 3.5 (OTB releases 6.4 to 7.4) or Python 3.7 (since OTB 8)"
+                    "You need Python 3.5 (OTB 6.4 to 7.4) or Python 3.7 (since OTB 8)"
                 )
             else:
                 logger.critical(
                     "It seems that your env variables aren't properly set,"
                     " first use 'call otbenv.bat' then try to import pyotb once again"
                 )
-    docs_link = "https://www.orfeo-toolbox.org/CookBook/Installation.html"
+    elif error_message.startswith("libpython3."):
+        logger.critical(
+            "It seems like you need to symlink or recompile python bindings"
+        )
+        if (
+            sys.executable.startswith("/usr/bin")
+            and which("ctest")
+            and which("python3-config")
+        ):
+            logger.critical(
+                "To compile, use 'cd %s ; source otbenv.profile ; "
+                "ctest -S share/otb/swig/build_wrapping.cmake -VV'",
+                prefix,
+            )
+            return
+        logger.critical(
+            "You may need to install cmake, python3-dev and mesa's libgl"
+            " in order to recompile python bindings"
+        )
+        expected = int(error_message[11])
+        if expected != sys.version_info.minor:
+            logger.critical(
+                "Python library version mismatch (OTB expected 3.%s) : "
+                "a symlink may not work, depending on your python version",
+                expected,
+            )
+        lib_dir = sysconfig.get_config_var("LIBDIR")
+        lib = f"{lib_dir}/libpython3.{sys.version_info.minor}.so"
+        if Path(lib).exists():
+            target = f"{prefix}/lib/libpython3.{expected}.so.1.0"
+            logger.critical("If using OTB>=8.0, try 'ln -sf %s %s'", lib, target)
     logger.critical(
-        "You can verify installation requirements for your OS at %s", docs_link
+        "You can verify installation requirements for your OS at %s", DOCS_URL
     )
 
 
-# Since helpers is the first module to be inititialized, this will prevent pyotb to run if OTB is not found
+# This part of pyotb is the first imported during __init__ and checks if OTB is found
+# If OTB is not found, a SystemExit is raised, to prevent execution of the core module
 find_otb()
diff --git a/pyotb/install.py b/pyotb/install.py
new file mode 100644
index 0000000000000000000000000000000000000000..47a6ee7252c02afa8d4f1957ba694ed20cbfcd06
--- /dev/null
+++ b/pyotb/install.py
@@ -0,0 +1,199 @@
+"""This module contains functions for interactive auto installation of OTB."""
+import json
+import os
+import re
+import subprocess
+import sys
+import sysconfig
+import tempfile
+import urllib.request
+import zipfile
+from pathlib import Path
+from shutil import which
+
+
+def interactive_config():
+    """Prompt user to configure installation variables."""
+    version = input("Choose a version to download (default is latest): ")
+    default_dir = Path.home() / "Applications"
+    path = input(f"Parent directory for installation (default is {default_dir}): ")
+    env = input("Permanently change user's environment variables ? (y/n): ") == "y"
+    return version, path, env
+
+
+def otb_latest_release_tag():
+    """Use gitlab API to find latest release tag name, but skip pre-releases."""
+    api_endpoint = "https://gitlab.orfeo-toolbox.org/api/v4/projects/53/repository/tags"
+    vers_regex = re.compile(r"^\d\.\d\.\d$")  # we ignore rc-* or alpha-*
+    with urllib.request.urlopen(api_endpoint) as stream:
+        data = json.loads(stream.read())
+    releases = sorted(
+        [tag["name"] for tag in data if vers_regex.match(tag["name"])],
+    )
+    return releases[-1]
+
+
+def check_versions(sysname: str, python_minor: int, otb_major: int):
+    """Verify if python version is compatible with major OTB version.
+
+    Args:
+        sysname: OTB's system name convention (Linux64, Darwin64, Win64)
+        python_minor: minor version of python
+        otb_major: major version of OTB to be installed
+    Returns:
+        (True, 0) or (False, expected_version) if case of version conflict
+    """
+    if sysname == "Win64":
+        expected = 5 if otb_major in (6, 7) else 7
+        if python_minor == expected:
+            return True, 0
+    elif sysname == "Darwin64":
+        expected = 7, 0
+        if python_minor == expected:
+            return True, 0
+    elif sysname == "Linux64":
+        expected = 5 if otb_major in (6, 7) else 8
+        if python_minor == expected:
+            return True, 0
+    return False, expected
+
+
+def env_config_unix(otb_path: Path):
+    """Update env profile for current user with new otb_env.profile call.
+
+    Args:
+        otb_path: the path of the new OTB installation
+
+    """
+    profile = Path.home() / ".profile"
+    with profile.open("a", encoding="utf-8") as buf:
+        buf.write(f'\n. "{otb_path}/otbenv.profile"\n')
+        print(f"##### Added new environment variables to {profile}")
+
+
+def env_config_windows(otb_path: Path):
+    """Update user's registry hive with new OTB_ROOT env variable.
+
+    Args:
+        otb_path: path of the new OTB installation
+
+    """
+    import winreg  # pylint: disable=import-error,import-outside-toplevel
+
+    with winreg.OpenKeyEx(
+        winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_SET_VALUE
+    ) as reg_key:
+        winreg.SetValueEx(reg_key, "OTB_ROOT", 0, winreg.REG_EXPAND_SZ, str(otb_path))
+        print(
+            "##### Environment variable 'OTB_ROOT' added to user's registry. "
+            "You'll need to login / logout to apply this change."
+        )
+        reg_cmd = "reg.exe delete HKEY_CURRENT_USER\\Environment /v OTB_ROOT /f"
+        print(f"To undo this, you may use '{reg_cmd}'")
+
+
+def install_otb(version: str = "latest", path: str = "", edit_env: bool = True):
+    """Install pre-compiled OTB binaries in path, use latest release by default.
+
+    Args:
+        version: OTB version tag, e.g. '8.1.2'
+        path: installation directory, default is $HOME/Applications
+        edit_env: whether or not to permanently modify user's environment variables
+
+    Returns:
+        full path of the new installation
+
+    """
+    # Read env config
+    if sys.version_info.major == 2:
+        raise SystemExit("Python 3 is required for OTB bindings.")
+    python_minor = sys.version_info.minor
+    if not version or version == "latest":
+        version = otb_latest_release_tag()
+    name_corresp = {"linux": "Linux64", "darwin": "Darwin64", "win32": "Win64"}
+    sysname = name_corresp[sys.platform]
+    ext = "zip" if sysname == "Win64" else "run"
+    cmd = which("zsh") or which("bash") or which("sh")
+    otb_major = int(version[0])
+    check, expected = check_versions(sysname, python_minor, otb_major)
+    if sysname == "Win64" and not check:
+        raise SystemExit(
+            f"Python 3.{expected} is required to import bindings on Windows."
+        )
+    # Fetch archive and run installer
+    filename = f"OTB-{version}-{sysname}.{ext}"
+    url = f"https://www.orfeo-toolbox.org/packages/archives/OTB/{filename}"
+    tmpdir = tempfile.gettempdir()
+    tmpfile = Path(tmpdir) / filename
+    print(f"##### Downloading {url}")
+    urllib.request.urlretrieve(url, tmpfile)
+    if path:
+        default_path = False
+        path = Path(path)
+    else:
+        default_path = True
+        path = Path.home() / "Applications" / tmpfile.stem
+    if sysname == "Win64":
+        with zipfile.ZipFile(tmpfile) as zipf:
+            print("##### Extracting zip file")
+            # Unzip will always create a dir with OTB-version name
+            zipf.extractall(path.parent if default_path else path)
+    else:
+        install_cmd = f"{cmd} {tmpfile} --target {path} --accept"
+        print(f"##### Executing '{install_cmd}'\n")
+        subprocess.run(install_cmd, shell=True, check=True)
+    tmpfile.unlink()  # cleaning
+
+    # Add env variable to profile
+    if edit_env:
+        if sysname == "Win64":
+            env_config_windows(path)
+        else:
+            env_config_unix(path)
+    elif not default_path:
+        ext = "bat" if sysname == "Win64" else "profile"
+        print(
+            f"Remember to call '{path}{os.sep}otbenv.{ext}' before importing pyotb, "
+            f"or add 'OTB_ROOT=\"{path}\"' to your env variables."
+        )
+    # Requirements are met, no recompilation or symlink required
+    if check:
+        return str(path)
+
+    # Else try recompile bindings : can fail because of OpenGL
+    # Here we check for /usr/bin because CMake's will find_package() only there
+    if (
+        sys.executable.startswith("/usr/bin")
+        and which("ctest")
+        and which("python3-config")
+    ):
+        try:
+            print("\n!!!!! Python version mismatch, trying to recompile bindings")
+            ctest_cmd = (
+                ". ./otbenv.profile && ctest -S share/otb/swig/build_wrapping.cmake -V"
+            )
+            print(f"##### Executing '{ctest_cmd}'")
+            subprocess.run(ctest_cmd, cwd=path, check=True, shell=True)
+            return str(path)
+        except subprocess.CalledProcessError:
+            print("\nCompilation failed.")
+    # TODO: support for sudo auto build deps install using apt, pacman/yay, brew...
+    print(
+        "You need cmake, python3-dev and libgl1-mesa-dev installed."
+        "\nTrying to symlink libraries instead - this may fail with newest versions."
+    )
+
+    # Finally try with cross version python symlink (only tested on Ubuntu)
+    suffix = "so.1.0" if otb_major >= 8 else f"so.rh-python3{expected}-1.0"
+    target_lib = f"{path}/lib/libpython3.{expected}.{suffix}"
+    lib_dir = sysconfig.get_config_var("LIBDIR")
+    lib = f"{lib_dir}/libpython3.{sys.version_info.minor}.so"
+    if Path(lib).exists():
+        print(f"##### Creating symbolic link: {lib} -> {target_lib}")
+        ln_cmd = f'ln -sf "{lib}" "{target_lib}"'
+        subprocess.run(ln_cmd, executable=cmd, shell=True, check=True)
+        return str(path)
+    raise SystemError(
+        f"Unable to automatically locate library for executable '{sys.executable}', "
+        f"you could manually create a symlink from that file to {target_lib}"
+    )
diff --git a/pyproject.toml b/pyproject.toml
index 30804d40d843c4fc8d6f1a8dd017e40092fe2510..fdaa0a35167bea61244a4d4cfb500c2bbf7cd7d2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -56,7 +56,6 @@ max-line-length = 88
 max-module-lines = 2000
 good-names = ["x", "y", "i", "j", "k", "e"]
 disable = [
-    "fixme",
     "line-too-long",
     "too-many-locals",
     "too-many-branches",