adding fatjar and build goals
diff --git a/.gitignore b/.gitignore
index ed7ceec..72ac814 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 *.iws
 *.ipr
 .asciidoctor
+.site-content
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java b/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
index 9fa9dbe..0ae9425 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/Ripener.java
@@ -15,10 +15,12 @@
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
+import static java.util.Optional.ofNullable;
 import static java.util.stream.Collectors.toList;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -194,7 +196,11 @@
             final AtomicLong bundleIdGenerator = new AtomicLong(1);
             Stream.concat(scanner.findOSGiBundles().stream(), scanner.findPotentialOSGiBundles().stream())
                     .sorted(this::compareBundles)
-                    .map(it -> new OSGiBundleLifecycle(it.getManifest(), it.getJar(), services, registry, configuration, bundleIdGenerator.getAndIncrement()))
+                    .map(it -> new OSGiBundleLifecycle(
+                            it.getManifest(), it.getJar(),
+                            services, registry, configuration,
+                            bundleIdGenerator.getAndIncrement(),
+                            it.getFiles()))
                     .peek(OSGiBundleLifecycle::start)
                     .peek(it -> registry.getBundles().put(it.getBundle().getBundleId(), it))
                     .forEach(bundle -> LOGGER.debug("Bundle {}", bundle));
@@ -276,7 +282,61 @@
 
     static void main(final String[] args) {
         final CountDownLatch latch = new CountDownLatch(1);
-        final Ripener main = new Impl(new Configuration()).start();
+        final Configuration configuration = new Configuration();
+        ofNullable(System.getProperty("winegrower.ripener.configuration.workdir"))
+                .map(String::valueOf)
+                .map(File::new)
+                .ifPresent(configuration::setWorkDir);
+        ofNullable(System.getProperty("winegrower.ripener.configuration.prioritizedBundles"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .map(it -> asList(it.split(",")))
+                .ifPresent(configuration::setPrioritizedBundles);
+        ofNullable(System.getProperty("winegrower.ripener.configuration.ignoredBundles"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .map(it -> asList(it.split(",")))
+                .ifPresent(configuration::setIgnoredBundles);
+        ofNullable(System.getProperty("winegrower.ripener.configuration.scanningIncludes"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .map(it -> asList(it.split(",")))
+                .ifPresent(configuration::setScanningIncludes);
+        ofNullable(System.getProperty("winegrower.ripener.configuration.scanningExcludes"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .map(it -> asList(it.split(",")))
+                .ifPresent(configuration::setScanningExcludes);
+        ofNullable(System.getProperty("winegrower.ripener.configuration.manifestContributors"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .map(it -> asList(it.split(",")))
+                .ifPresent(contributors -> {
+                    configuration.setManifestContributors(contributors.stream().map(clazz -> {
+                        try {
+                            return Thread.currentThread().getContextClassLoader().loadClass(clazz).getConstructor().newInstance();
+                        } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException
+                                | ClassNotFoundException e) {
+                            throw new IllegalArgumentException(e);
+                        } catch (final InvocationTargetException e) {
+                            throw new IllegalArgumentException(e.getTargetException());
+                        }
+                    }).map(ManifestContributor.class::cast).collect(toList()));
+                });
+        ofNullable(System.getProperty("winegrower.ripener.configuration.jarFilter"))
+                .map(String::valueOf)
+                .filter(it -> !it.isEmpty())
+                .ifPresent(filter -> {
+                    try {
+                        configuration.setJarFilter((Predicate<String>) Thread.currentThread().getContextClassLoader().loadClass(filter)
+                                .getConstructor().newInstance());
+                    } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
+                        throw new IllegalArgumentException(e);
+                    } catch (final InvocationTargetException e) {
+                        throw new IllegalArgumentException(e.getTargetException());
+                    }
+                });
+        final Ripener main = new Impl(configuration).start();
         Runtime.getRuntime().addShutdownHook(new Thread() {
 
             {
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleImpl.java b/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleImpl.java
index d523b69..721109e 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleImpl.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/deployer/BundleImpl.java
@@ -64,15 +64,19 @@
     private final String symbolicName;
     private final Dictionary<String, String> headers;
     private final File dataFileBase;
+    private final Collection<String> includedResources;
     private int state = Bundle.UNINSTALLED;
 
     BundleImpl(final Manifest manifest, final File file, final BundleContextImpl context,
-               final Ripener.Configuration configuration, final long id) {
+               final Ripener.Configuration configuration, final long id,
+               final Collection<String> includedResources) {
         this.file = file;
-        this.dataFileBase = new File(configuration.getWorkDir(), file.getName());
+        this.dataFileBase = new File(configuration.getWorkDir(),
+                file == null ? Long.toString(System.identityHashCode(manifest)) : file.getName());
         this.context = context;
         this.id = id;
         this.loader = Thread.currentThread().getContextClassLoader();
+        this.includedResources = includedResources;
         this.version = ofNullable(manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION))
             .map(Version::new)
             .orElse(Version.emptyVersion);
@@ -161,7 +165,7 @@
 
     @Override
     public String getLocation() {
-        return file.getAbsolutePath();
+        return includedResources != null ? null : file.getAbsolutePath();
     }
 
     @Override
@@ -209,6 +213,11 @@
 
     @Override
     public Enumeration<String> getEntryPaths(final String path) {
+        if (includedResources != null) {
+            return enumeration(includedResources.stream()
+                    .filter(it -> it.startsWith(path))
+                    .collect(toList()));
+        }
         if (file.isDirectory()) {
             final Path base = file.toPath().toAbsolutePath();
             final Path subPath = new File(file, path == null ? "" : (path.startsWith("/") ? path.substring(1) : path)).toPath();
@@ -245,7 +254,7 @@
 
     @Override
     public long getLastModified() {
-        return file.lastModified();
+        return file == null ? -1 : file.lastModified();
     }
 
     @Override
@@ -253,6 +262,16 @@
         final Filter filter = filePattern == null ?
                 null : context.createFilter("(filename=" + filePattern + ")");
         final String prefix = path == null ? "" : (path.startsWith("/") ? path.substring(1) : path);
+
+        if (includedResources != null) {
+            if (!recurse) {
+                return enumeration(includedResources.stream()
+                    .filter(it -> doFilterEntry(filter, prefix, it))
+                    .map(loader::getResource)
+                    .collect(toList()));
+            }
+        }
+
         final File baseFile = new File(file, prefix);
         final Path base = baseFile.toPath();
         final Path filePath = this.file.toPath();
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/deployer/OSGiBundleLifecycle.java b/winegrower-core/src/main/java/org/apache/winegrower/deployer/OSGiBundleLifecycle.java
index ca1c3b3..82efc3c 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/deployer/OSGiBundleLifecycle.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/deployer/OSGiBundleLifecycle.java
@@ -15,6 +15,7 @@
 
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
 import java.util.jar.Manifest;
 
 import org.apache.winegrower.Ripener;
@@ -34,9 +35,9 @@
 
     public OSGiBundleLifecycle(final Manifest manifest, final File file, final OSGiServices services,
                                final BundleRegistry registry, final Ripener.Configuration configuration,
-                               final long id) {
+                               final long id, final Collection<String> includedResources) {
         this.context = new BundleContextImpl(manifest, services, this::getBundle, registry);
-        this.bundle = new BundleImpl(manifest, file, context, configuration, id);
+        this.bundle = new BundleImpl(manifest, file, context, configuration, id, includedResources);
     }
 
     public BundleActivatorHandler getActivator() {
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java b/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
index 7817fc6..03d721a 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/scanner/StandaloneScanner.java
@@ -13,20 +13,29 @@
  */
 package org.apache.winegrower.scanner;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.list;
+import static java.util.function.Function.identity;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
 import static org.apache.xbean.finder.archive.ClasspathArchive.archive;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
+import java.util.stream.Stream;
 
 import org.apache.winegrower.Ripener;
 import org.apache.winegrower.scanner.manifest.ManifestCreator;
@@ -46,6 +55,8 @@
     private final Ripener.Configuration configuration;
     private final ClassLoader loader;
     private final File frameworkJar;
+    private final Map<String, Manifest> providedManifests;
+    private final Map<String, List<String>> providedIndex;
 
     public StandaloneScanner(final Ripener.Configuration configuration, final File frameworkJar) {
         this.configuration = configuration;
@@ -58,6 +69,47 @@
         } catch (final IOException e) {
             throw new IllegalStateException(e);
         }
+
+        try { // fatjar plugin
+            providedManifests = list(this.loader.getResources("WINEGROWER-INF/manifests.properties")).stream()
+                .flatMap(url -> {
+                    final Properties properties = new Properties();
+                    try (final InputStream stream = url.openStream()) {
+                        properties.load(stream);
+                    } catch (final IOException e) {
+                        throw new IllegalArgumentException(e);
+                    }
+                    return properties.stringPropertyNames().stream()
+                            .collect(toMap(identity(), key -> {
+                                final String property = properties.getProperty(key);
+                                final Manifest manifest = new Manifest();
+                                try (final ByteArrayInputStream mfStream = new ByteArrayInputStream(property.getBytes(StandardCharsets.UTF_8))) {
+                                    manifest.read(mfStream);
+                                } catch (final IOException e) {
+                                    throw new IllegalArgumentException(e);
+                                }
+                                return manifest;
+                            })).entrySet().stream();
+                })
+                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+            providedIndex = list(this.loader.getResources("WINEGROWER-INF/index.properties")).stream()
+                    .flatMap(url -> {
+                        final Properties properties = new Properties();
+                        try (final InputStream stream = url.openStream()) {
+                            properties.load(stream);
+                        } catch (final IOException e) {
+                            throw new IllegalArgumentException(e);
+                        }
+                        return properties.stringPropertyNames().stream()
+                                .collect(toMap(identity(), key -> {
+                                    final String property = properties.getProperty(key);
+                                    return asList(property.split(","));
+                                })).entrySet().stream();
+                    })
+                    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+        } catch (final IOException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
 
     public Collection<BundleDefinition> findPotentialOSGiBundles() {
@@ -81,7 +133,7 @@
                           return null;
                       }
                       LOGGER.debug("{} was scanned and is converted to a bundle", it.file);
-                      return new BundleDefinition(manifest, it.file);
+                      return new BundleDefinition(manifest, it.file, null);
                   } catch (final LinkageError e) {
                       LOGGER.debug("{} is not scannable, maybe exclude it in framework configuration", it.file);
                       return null;
@@ -92,13 +144,15 @@
     }
 
     public Collection<BundleDefinition> findOSGiBundles() {
-        return urls
-                .stream()
-                .map(Files::toFile)
-                .filter(this::isIncluded)
-                .filter(it -> this.configuration.getIgnoredBundles().stream().noneMatch(ex -> it.getName().startsWith(ex)))
-                .map(this::toDefinition)
-                .filter(Objects::nonNull)
+        return Stream.concat(
+                    urls.stream()
+                        .map(Files::toFile)
+                        .filter(this::isIncluded)
+                        .filter(it -> this.configuration.getIgnoredBundles().stream().noneMatch(ex -> it.getName().startsWith(ex)))
+                        .map(this::toDefinition)
+                        .filter(Objects::nonNull),
+                    providedManifests.entrySet().stream()
+                        .map(it -> new BundleDefinition(it.getValue(), null, providedIndex.get(it.getKey()))))
                 .collect(toList());
     }
 
@@ -113,7 +167,7 @@
                 try (final InputStream stream = new FileInputStream(manifest)) {
                     final Manifest mf = new Manifest(stream);
                     if (isOSGi(mf)) {
-                        return new BundleDefinition(mf, file);
+                        return new BundleDefinition(mf, file, null);
                     }
                     return null;
                 } catch (final IOException e) {
@@ -128,7 +182,7 @@
                 return null;
             }
             if (isOSGi(manifest)) {
-                return new BundleDefinition(manifest, file);
+                return new BundleDefinition(manifest, file, null);
             }
             return null;
         } catch (final Exception e) {
@@ -143,10 +197,16 @@
     public static class BundleDefinition {
         private final Manifest manifest;
         private final File jar;
+        private final Collection<String> files;
 
-        private BundleDefinition(final Manifest manifest, final File jar) {
+        private BundleDefinition(final Manifest manifest, final File jar, final Collection<String> files) {
             this.manifest = manifest;
             this.jar = jar;
+            this.files = files;
+        }
+
+        public Collection<String> getFiles() {
+            return files;
         }
 
         public Manifest getManifest() {
diff --git a/winegrower-core/src/main/java/org/apache/winegrower/service/BundleRegistry.java b/winegrower-core/src/main/java/org/apache/winegrower/service/BundleRegistry.java
index cdeb85e..1352de7 100644
--- a/winegrower-core/src/main/java/org/apache/winegrower/service/BundleRegistry.java
+++ b/winegrower-core/src/main/java/org/apache/winegrower/service/BundleRegistry.java
@@ -40,7 +40,7 @@
         frameworkManifest.getMainAttributes().putValue("Bundle-Version", "1.0");
         frameworkManifest.getMainAttributes().putValue("Bundle-SymbolicName", "Ripener");
         final OSGiBundleLifecycle frameworkBundle = new OSGiBundleLifecycle(
-                frameworkManifest, framework, services, this, configuration, 0L);
+                frameworkManifest, framework, services, this, configuration, 0L, null);
         frameworkBundle.start();
         bundles.put(0L, frameworkBundle);
     }
diff --git a/winegrower-core/src/test/java/org/apache/winegrower/deployer/BundleImplTest.java b/winegrower-core/src/test/java/org/apache/winegrower/deployer/BundleImplTest.java
index db93eb1..232fbb9 100644
--- a/winegrower-core/src/test/java/org/apache/winegrower/deployer/BundleImplTest.java
+++ b/winegrower-core/src/test/java/org/apache/winegrower/deployer/BundleImplTest.java
@@ -49,8 +49,8 @@
         registry = new BundleRegistry(services, configuration);
         final BundleContextImpl context = new BundleContextImpl(manifest, services, () -> bundle, registry);
         final File file = new File(registry.getFramework().getParentFile(), "test-classes");
-        bundle = new BundleImpl(manifest, file, context, configuration, 1);
-        registry.getBundles().put(bundle.getBundleId(), new OSGiBundleLifecycle(manifest, file, services, registry, configuration, 1));
+        bundle = new BundleImpl(manifest, file, context, configuration, 1, null);
+        registry.getBundles().put(bundle.getBundleId(), new OSGiBundleLifecycle(manifest, file, services, registry, configuration, 1, null));
     }
 
     @Test
diff --git a/winegrower-documentation/pom.xml b/winegrower-documentation/pom.xml
index 9330201..c94ffe1 100644
--- a/winegrower-documentation/pom.xml
+++ b/winegrower-documentation/pom.xml
@@ -35,20 +35,6 @@
     <groovy.version>2.5.3</groovy.version>
   </properties>
 
-  <dependencies>
-
-    <dependency>
-      <groupId>org.codehaus.groovy</groupId>
-      <artifactId>groovy-all</artifactId>
-      <version>${groovy.version}</version>
-      <type>pom</type>
-    </dependency>
-    <dependency>
-      <groupId>org.asciidoctor</groupId>
-      <artifactId>asciidoctorj</artifactId>
-      <version>1.5.7</version>
-    </dependency>
-  </dependencies>
   <build>
     <plugins>
       <plugin>
@@ -118,37 +104,26 @@
       <build>
         <plugins>
           <plugin>
-            <groupId>org.codehaus.gmavenplus</groupId>
-            <artifactId>gmavenplus-plugin</artifactId>
-            <version>1.6</version>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-scm-publish-plugin</artifactId>
+            <version>1.1</version>
             <executions>
               <execution>
-                <id>deploy-site</id>
-                <phase>none</phase>
+                <id>scm-publish</id>
+                <phase>site-deploy</phase>
                 <goals>
-                  <goal>execute</goal>
+                  <goal>publish-scm</goal>
                 </goals>
-                <configuration>
-                  <allowSystemExits>true</allowSystemExits>
-                  <scripts>
-                    <script>${project.basedir}/src/build/GithubPages.groovy</script>
-                  </scripts>
-                </configuration>
               </execution>
             </executions>
-            <dependencies>
-              <dependency>
-                <groupId>org.eclipse.jgit</groupId>
-                <artifactId>org.eclipse.jgit</artifactId>
-                <version>4.9.2.201712150930-r</version>
-              </dependency>
-              <dependency>
-                <groupId>org.codehaus.groovy</groupId>
-                <artifactId>groovy-all</artifactId>
-                <version>${groovy.version}</version>
-                <type>pom</type>
-              </dependency>
-            </dependencies>
+            <configuration>
+              <content>${project.build.directory}/documentation</content>
+              <pubScmUrl>scm:git:${project.scm.connection}</pubScmUrl>
+              <scmBranch>gh-pages</scmBranch>
+              <tryUpdate>true</tryUpdate>
+              <checkoutDirectory>${project.parent.basedir}/.site-content</checkoutDirectory>
+              <serverId>${github.serverId}</serverId>
+            </configuration>
           </plugin>
         </plugins>
       </build>
diff --git a/winegrower-documentation/src/build/GithubPages.groovy b/winegrower-documentation/src/build/GithubPages.groovy
deleted file mode 100644
index 8892443..0000000
--- a/winegrower-documentation/src/build/GithubPages.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.winegrower.build
-
-import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest
-import org.apache.maven.settings.crypto.SettingsDecrypter
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
-
-import static java.util.Collections.singleton
-
-def source = new File(project.build.directory, 'documentation')
-if (!source.exists() || !new File(source, 'index.html').exists()) {
-    log.warn('Not ready to deploy, skipping')
-    return
-}
-
-def branch = 'refs/heads/gh-pages'
-def workDir = new File(project.build.directory, UUID.randomUUID().toString() + '_' + System.currentTimeMillis())
-
-def url = project.parent.scm.url
-def serverId = project.properties['github.serverId']
-log.info("Using server ${serverId}")
-
-def server = session.settings.servers.findAll { it.id == serverId }.iterator().next()
-def decryptedServer = session.container.lookup(SettingsDecrypter).decrypt(new DefaultSettingsDecryptionRequest(server))
-server = decryptedServer.server != null ? decryptedServer.server : server
-
-log.info("Using url=${url}")
-log.info("Using user=${server.username}")
-log.info("Using branch=${branch}")
-
-def credentialsProvider = new UsernamePasswordCredentialsProvider(server.username, server.password)
-def git = Git.cloneRepository()
-        .setCredentialsProvider(credentialsProvider)
-        .setURI(url)
-        .setDirectory(workDir)
-        .setBranchesToClone(singleton(branch))
-        .setBranch(branch)
-        .call()
-
-new AntBuilder().copy(todir: workDir.absolutePath, overwrite: true) {
-    fileset(dir: source.absolutePath)
-}
-// we don't drop old files, stay conservative for now
-
-def message = "Updating the documentation for version ${project.version} // " + new Date().toString()
-git.add().addFilepattern(".").call()
-git.commit().setAll(true).setMessage(message).call()
-git.status().call()
-git.push().setCredentialsProvider(credentialsProvider).add(branch).call()
-log.info("Updated the documentation on ${new Date()}")
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/pom.xml b/winegrower-extension/winegrower-build/winegrower-build-common/pom.xml
index ad166dd..362fbc7 100644
--- a/winegrower-extension/winegrower-build/winegrower-build-common/pom.xml
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/pom.xml
@@ -42,6 +42,16 @@
       <scope>compile</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-compress</artifactId>
+      <version>1.18</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-text</artifactId>
+      <version>1.6</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.winegrower</groupId>
       <artifactId>winegrower-testing-junit5</artifactId>
       <version>${project.version}</version>
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/Build.java b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/Build.java
new file mode 100644
index 0000000..89a40d8
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/Build.java
@@ -0,0 +1,269 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.common;
+
+import static java.util.Arrays.asList;
+import static java.util.Locale.ENGLISH;
+import static java.util.stream.Collectors.joining;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.stream.Stream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.text.StringSubstitutor;
+
+public class Build implements Runnable {
+    private final Configuration configuration;
+
+    public Build(final Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    @Override
+    public void run() {
+        final File distroFolder = new File(configuration.workDir, configuration.artifactId + "-winegrower-distribution");
+        if (distroFolder.exists()) {
+            delete(distroFolder);
+        }
+
+        Stream.of("bin", "conf", "logs", "lib").forEach(i -> new File(distroFolder, i).mkdirs());
+
+        for (final String ext : asList("sh", "bat")) {
+            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    Thread.currentThread().getContextClassLoader().getResourceAsStream("bin/winegrower." + ext)))) {
+                write(new File(distroFolder, "bin/winegrower." + ext), StringSubstitutor.replace(reader.lines().collect(joining("\n")),
+                        new HashMap<String, String>() {{
+                            put("main", configuration.main);
+                        }}));
+            } catch (final IOException e) {
+                throw new IllegalStateException(e.getMessage(), e);
+            }
+        }
+
+        copyProvidedFiles(configuration.basedir, distroFolder);
+
+        Stream.of("conf", "logs").forEach(folder ->
+                write(new File(distroFolder, folder + "/you_can_safely_delete.txt"), "Just there to not loose the folder cause it is empty, you can safely delete."));
+
+        configuration.jars.forEach(it -> addLib(distroFolder, it));
+
+        final Path prefix = configuration.skipArchiveRootFolder ? distroFolder.toPath() : distroFolder.getParentFile().toPath();
+        for (final String format : configuration.formats) {
+            final File output = new File(configuration.workDir, configuration.artifactId + "-winegrower-distribution." + format);
+
+            switch (format.toLowerCase(ENGLISH)) {
+                case "tar.gz":
+                    try (final TarArchiveOutputStream tarGz =
+                                 new TarArchiveOutputStream(new GZIPOutputStream(new FileOutputStream(output)))) {
+                        tarGz.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+                        for (final String entry : distroFolder.list()) {
+                            tarGz(tarGz, new File(distroFolder, entry), prefix);
+                        }
+                    } catch (final IOException e) {
+                        throw new IllegalStateException(e.getMessage(), e);
+                    }
+                    break;
+                case "zip":
+                    try (final ZipArchiveOutputStream zos =
+                                 new ZipArchiveOutputStream(new FileOutputStream(output))) {
+                        for (final String entry : distroFolder.list()) {
+                            zip(zos, new File(distroFolder, entry), prefix);
+                        }
+                    } catch (final IOException e) {
+                        throw new IllegalStateException(e.getMessage(), e);
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException(format + " is not supported");
+            }
+        }
+
+        if (!configuration.keepExplodedFolder) {
+            delete(distroFolder);
+        }
+    }
+
+    private void copyProvidedFiles(final File basedir, final File base) {
+        final File srcConf = new File(basedir, configuration.conf);
+        if (srcConf.exists() && srcConf.isDirectory()) {
+            final File targetConf = new File(base, "conf");
+            targetConf.mkdirs();
+
+            for (final File file : srcConf.listFiles()) {
+                final String fileName = file.getName();
+                if (fileName.startsWith(".")) {
+                    // hidden file -> ignore
+                    continue;
+                }
+
+                try {
+                    Files.copy(file.toPath(), new File(targetConf, fileName).toPath());
+                } catch (final IOException e) {
+                    throw new IllegalStateException("Could not copy file " + file.getAbsolutePath(), e);
+                }
+            }
+        }
+
+        final File srcBin = new File(basedir, configuration.bin);
+        if (srcBin.exists() && srcBin.isDirectory()) {
+            final File targetRoot = new File(base, "bin");
+            targetRoot.mkdirs();
+            Stream.of(srcBin.listFiles())
+                    .filter(f -> !f.isDirectory()) // not nested for now
+                    .forEach(f -> {
+                        try {
+                            final File target = new File(targetRoot, f.getName());
+                            Files.copy(f.toPath(), target.toPath());
+                            if (target.getName().endsWith(".sh")) {
+                                target.setExecutable(true);
+                            }
+                        } catch (final IOException e) {
+                            throw new IllegalArgumentException("Could not copy file " + f.getAbsolutePath(), e);
+                        }
+                    });
+        }
+    }
+
+    private void write(final File file, final String content) {
+        try {
+            Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private void delete(final File file) { // not critical
+        final Path rootPath = file.toPath();
+        try {
+            Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+                    Files.delete(file);
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
+                    Files.delete(dir);
+                    return FileVisitResult.CONTINUE;
+                }
+
+            });
+        } catch (final IOException e) {
+            // no-op
+        }
+    }
+
+    private void addLib(final File base, final File cc) {
+        try {
+            Files.copy(cc.toPath(), new File(base, "lib/" + cc.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
+        } catch (final IOException e) {
+            throw new IllegalStateException(e.getMessage(), e);
+        }
+    }
+
+    private void zip(final ZipArchiveOutputStream zip, final File f, final Path prefix) throws IOException {
+        final String path = prefix.relativize(f.toPath()).toString().replace(File.separator, "/");
+        final ZipArchiveEntry archiveEntry = new ZipArchiveEntry(f, path);
+        if (isSh(path)) {
+            archiveEntry.setUnixMode(0755);
+        }
+        zip.putArchiveEntry(archiveEntry);
+        if (f.isDirectory()) {
+            zip.closeArchiveEntry();
+            final File[] files = f.listFiles();
+            if (files != null) {
+                for (final File child : files) {
+                    zip(zip, child, prefix);
+                }
+            }
+        } else {
+            Files.copy(f.toPath(), zip);
+            zip.closeArchiveEntry();
+        }
+    }
+
+    private void tarGz(final TarArchiveOutputStream tarGz, final File f, final Path prefix) throws IOException {
+        final String path = prefix.relativize(f.toPath()).toString().replace(File.separator, "/");
+        final TarArchiveEntry archiveEntry = new TarArchiveEntry(f, path);
+        if (isSh(path)) {
+            archiveEntry.setMode(0755);
+        }
+        tarGz.putArchiveEntry(archiveEntry);
+        if (f.isDirectory()) {
+            tarGz.closeArchiveEntry();
+            final File[] files = f.listFiles();
+            if (files != null) {
+                for (final File child : files) {
+                    tarGz(tarGz, child, prefix);
+                }
+            }
+        } else {
+            Files.copy(f.toPath(), tarGz);
+            tarGz.closeArchiveEntry();
+        }
+    }
+
+    private boolean isSh(final String path) {
+        return path.endsWith(".sh");
+    }
+
+    public static class Configuration {
+        private final File workDir;
+        private final File basedir;
+        private final String artifactId;
+        private final Collection<File> jars;
+        private final Collection<String> formats;
+        private final String main;
+        private final String bin;
+        private final String conf;
+        private final boolean skipArchiveRootFolder;
+        private final boolean keepExplodedFolder;
+
+        public Configuration(final File workDir, final File basedir,
+                             final String artifactId, final Collection<File> jars,
+                             final Collection<String> formats,
+                             final String main, final String bin, final String conf,
+                             final boolean skipArchiveRootFolder, final boolean keepExplodedFolder) {
+            this.workDir = workDir;
+            this.basedir = basedir;
+            this.artifactId = artifactId;
+            this.jars = jars;
+            this.formats = formats;
+            this.main = main;
+            this.bin = bin;
+            this.conf = conf;
+            this.skipArchiveRootFolder = skipArchiveRootFolder;
+            this.keepExplodedFolder = keepExplodedFolder;
+        }
+    }
+}
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/FatJar.java b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/FatJar.java
new file mode 100644
index 0000000..25119c0
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/java/org/apache/winegrower/extension/build/common/FatJar.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.common;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+public class FatJar implements Runnable {
+    private final Configuration configuration;
+
+    public FatJar(final Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    @Override
+    public void run() {
+        requireNonNull(configuration.jars, "Jars are not set");
+        requireNonNull(configuration.output, "Output is not set").getParentFile().mkdirs();
+
+        try (final JarOutputStream outputStream = new JarOutputStream(
+                new BufferedOutputStream(new FileOutputStream(configuration.output)))) {
+            final Properties manifests = new Properties();
+            final Properties index = new Properties();
+            byte[] buffer = new byte[8192];
+            final Set<String> alreadyAdded = new HashSet<>();
+            configuration.jars.forEach(shadedJar -> {
+                final Collection<String> files = new ArrayList<>();
+                try (final JarInputStream inputStream = new JarInputStream(new BufferedInputStream(new FileInputStream(shadedJar)))) {
+                    final Manifest manifest = inputStream.getManifest();
+                    if (manifest != null) {
+                        try (final ByteArrayOutputStream manifestStream = new ByteArrayOutputStream()) {
+                            manifest.write(manifestStream);
+                            manifestStream.flush();
+                            manifests.put(shadedJar.getName(), new String(manifestStream.toByteArray(), StandardCharsets.UTF_8));
+                        }
+                    }
+
+                    ZipEntry nextEntry;
+                    while ((nextEntry = inputStream.getNextEntry()) != null) {
+                        final String name = nextEntry.getName();
+                        if (!alreadyAdded.add(name)) {
+                            continue;
+                        }
+                        files.add(name);
+                        outputStream.putNextEntry(nextEntry);
+                        int count;
+                        while ((count = inputStream.read(buffer, 0, buffer.length)) >= 0) {
+                            if (count > 0) {
+                                outputStream.write(buffer, 0, count);
+                            }
+                        }
+                        outputStream.closeEntry();
+                    }
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+                index.put(shadedJar.getName(), files.stream().collect(joining(",")));
+            });
+
+            outputStream.putNextEntry(new JarEntry("WINEGROWER-INF/"));
+            outputStream.closeEntry();
+
+            outputStream.putNextEntry(new JarEntry("WINEGROWER-INF/index.properties"));
+            index.store(outputStream, "index");
+            outputStream.closeEntry();
+
+            outputStream.putNextEntry(new JarEntry("WINEGROWER-INF/manifests.properties"));
+            manifests.store(outputStream, "manifests");
+            outputStream.closeEntry();
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public static class Configuration {
+        private final Collection<File> jars;
+        private final File output;
+
+        public Configuration(final Collection<File> jars, final File output) {
+            this.jars = jars;
+            this.output = output;
+        }
+    }
+}
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.bat b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.bat
new file mode 100644
index 0000000..0bbee0b
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.bat
@@ -0,0 +1,374 @@
+@echo off
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements.  See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License.  You may obtain a copy of the License at
+rem
+rem     http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem ---------------------------------------------------------------------------
+rem Start/Stop Script for the WINEGROWER Server
+rem
+rem Environment Variable Prerequisites
+rem
+rem   Do not set the variables in this script. Instead put them into a script
+rem   setenv.bat in WINEGROWER_BASE/bin to keep your customizations separate.
+rem
+rem   WHEN RUNNING WINEGROWER AS A WINDOWS SERVICE:
+rem   Note that the environment variables that affect the behavior of this
+rem   script will have no effect at all on Windows Services. As such, any
+rem   local customizations made in a WINEGROWER_BASE/bin/setenv.bat script
+rem   will also have no effect on Winegrower when launched as a Windows Service.
+rem   The configuration that controls Windows Services is stored in the Windows
+rem   Registry, and is most conveniently maintained using the "tomcatXw.exe"
+rem   maintenance utility, where "X" is the major version of Tomcat embedded
+rem   in Winegrower you are running.
+rem
+rem   WINEGROWER_HOME   May point at your WINEGROWER "build" directory.
+rem
+rem   WINEGROWER_BASE   (Optional) Base directory for resolving dynamic portions
+rem                   of a WINEGROWER installation.  If not present, resolves to
+rem                   the same directory that WINEGROWER_HOME points to.
+rem
+rem   WINEGROWER_OPTS   (Optional) Java runtime options used when the "start",
+rem                   "run" or "debug" command is executed.
+rem                   Include here and not in JAVA_OPTS all options, that should
+rem                   only be used by Winegrower itself, not by the stop process,
+rem                   the version command etc.
+rem                   Examples are heap size, GC logging, JMX ports etc.
+rem
+rem   WINEGROWER_TMPDIR (Optional) Directory path location of temporary directory
+rem                   the JVM should use (java.io.tmpdir).  Defaults to
+rem                   %WINEGROWER_BASE%\temp.
+rem
+rem   JAVA_HOME       Must point at your Java Development Kit installation.
+rem                   Required to run the with the "debug" argument.
+rem
+rem   JRE_HOME        Must point at your Java Runtime installation.
+rem                   Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME
+rem                   are both set, JRE_HOME is used.
+rem
+rem   JAVA_OPTS       (Optional) Java runtime options used when any command
+rem                   is executed.
+rem                   Include here and not in WINEGROWER_OPTS all options, that
+rem                   should be used by Winegrower and also by the stop process,
+rem                   the version command etc.
+rem                   Most options should go into WINEGROWER_OPTS.
+rem
+rem   JAVA_ENDORSED_DIRS (Optional) Lists of of semi-colon separated directories
+rem                   containing some jars in order to allow replacement of APIs
+rem                   created outside of the JCP (i.e. DOM and SAX from W3C).
+rem                   It can also be used to update the XML parser implementation.
+rem                   This is only supported for Java <= 8.
+rem                   Defaults to $WINEGROWER_HOME/endorsed.
+rem
+rem   JPDA_TRANSPORT  (Optional) JPDA transport used when the "jpda start"
+rem                   command is executed. The default is "dt_socket".
+rem
+rem   JPDA_ADDRESS    (Optional) Java runtime options used when the "jpda start"
+rem                   command is executed. The default is localhost:8000.
+rem
+rem   JPDA_SUSPEND    (Optional) Java runtime options used when the "jpda start"
+rem                   command is executed. Specifies whether JVM should suspend
+rem                   execution immediately after startup. Default is "n".
+rem
+rem   JPDA_OPTS       (Optional) Java runtime options used when the "jpda start"
+rem                   command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS,
+rem                   and JPDA_SUSPEND are ignored. Thus, all required jpda
+rem                   options MUST be specified. The default is:
+rem
+rem                   -agentlib:jdwp=transport=%JPDA_TRANSPORT%,
+rem                       address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
+rem
+rem   JSSE_OPTS       (Optional) Java runtime options used to control the TLS
+rem                   implementation when JSSE is used. Default is:
+rem                   "-Djdk.tls.ephemeralDHKeySize=2048"
+rem
+rem   LOGGING_CONFIG  (Optional) Override Winegrower's logging config file
+rem                   Example (all one line)
+rem                   set LOGGING_CONFIG="-Djava.util.logging.config.file=%WINEGROWER_BASE%\conf\logging.properties"
+rem
+rem   LOGGING_MANAGER (Optional) Override Winegrower's logging manager
+rem                   Example (all one line)
+rem                   set LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"
+rem
+rem   TITLE           (Optional) Specify the title of Winegrower window. The default
+rem                   TITLE is Winegrower if it's not specified.
+rem                   Example (all one line)
+rem                   set TITLE=Winegrower.Cluster#1.Server#1 [%DATE% %TIME%]
+rem ---------------------------------------------------------------------------
+
+setlocal
+
+rem Suppress Terminate batch job on CTRL+C
+if not ""%1"" == ""run"" goto mainEntry
+if "%TEMP%" == "" goto mainEntry
+if exist "%TEMP%\%~nx0.run" goto mainEntry
+echo Y>"%TEMP%\%~nx0.run"
+if not exist "%TEMP%\%~nx0.run" goto mainEntry
+echo Y>"%TEMP%\%~nx0.Y"
+call "%~f0" %* <"%TEMP%\%~nx0.Y"
+rem Use provided errorlevel
+set RETVAL=%ERRORLEVEL%
+del /Q "%TEMP%\%~nx0.Y" >NUL 2>&1
+exit /B %RETVAL%
+:mainEntry
+del /Q "%TEMP%\%~nx0.run" >NUL 2>&1
+
+rem Guess WINEGROWER_HOME if not defined
+set "CURRENT_DIR=%cd%"
+if not "%WINEGROWER_HOME%" == "" goto gotHome
+set "WINEGROWER_HOME=%CURRENT_DIR%"
+if exist "%WINEGROWER_HOME%\bin\WINEGROWER.bat" goto okHome
+cd ..
+set "WINEGROWER_HOME=%cd%"
+cd "%CURRENT_DIR%"
+:gotHome
+
+if exist "%WINEGROWER_HOME%\bin\WINEGROWER.bat" goto okHome
+echo The WINEGROWER_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program
+goto end
+:okHome
+
+rem Copy WINEGROWER_BASE from WINEGROWER_HOME if not defined
+if not "%WINEGROWER_BASE%" == "" goto gotBase
+set "WINEGROWER_BASE=%WINEGROWER_HOME%"
+:gotBase
+
+rem Ensure that neither WINEGROWER_HOME nor WINEGROWER_BASE contains a semi-colon
+rem as this is used as the separator in the classpath and Java provides no
+rem mechanism for escaping if the same character appears in the path. Check this
+rem by replacing all occurrences of ';' with '' and checking that neither
+rem WINEGROWER_HOME nor WINEGROWER_BASE have changed
+if "%WINEGROWER_HOME%" == "%WINEGROWER_HOME:;=%" goto homeNoSemicolon
+echo Using WINEGROWER_HOME:   "%WINEGROWER_HOME%"
+echo Unable to start as WINEGROWER_HOME contains a semicolon (;) character
+goto end
+:homeNoSemicolon
+
+if "%WINEGROWER_BASE%" == "%WINEGROWER_BASE:;=%" goto baseNoSemicolon
+echo Using WINEGROWER_BASE:   "%WINEGROWER_BASE%"
+echo Unable to start as WINEGROWER_BASE contains a semicolon (;) character
+goto end
+:baseNoSemicolon
+
+rem Ensure that any user defined CLASSPATH variables are not used on startup,
+rem but allow them to be specified in setenv.bat, in rare case when it is needed.
+set CLASSPATH=
+
+rem Get standard environment variables
+if not exist "%WINEGROWER_BASE%\bin\setenv.bat" goto checkSetenvHome
+call "%WINEGROWER_BASE%\bin\setenv.bat"
+goto setenvDone
+:checkSetenvHome
+if exist "%WINEGROWER_HOME%\bin\setenv.bat" call "%WINEGROWER_HOME%\bin\setenv.bat"
+:setenvDone
+
+rem Get standard Java environment variables
+rem In debug mode we need a real JDK (JAVA_HOME)
+if ""%1"" == ""debug"" goto needJavaHome
+
+rem Otherwise either JRE or JDK are fine
+if not "%JRE_HOME%" == "" goto gotJreHome
+if not "%JAVA_HOME%" == "" goto gotJavaHome
+echo Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
+echo At least one of these environment variable is needed to run this program
+goto exit
+
+:needJavaHome
+rem Check if we have a usable JDK
+if "%JAVA_HOME%" == "" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\javaw.exe" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\jdb.exe" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\javac.exe" goto noJavaHome
+set "JRE_HOME=%JAVA_HOME%"
+goto okJava
+
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly.
+echo It is needed to run this program in debug mode.
+echo NB: JAVA_HOME should point to a JDK not a JRE.
+goto exit
+
+:gotJavaHome
+rem No JRE given, use JAVA_HOME as JRE_HOME
+set "JRE_HOME=%JAVA_HOME%"
+
+:gotJreHome
+rem Check if we have a usable JRE
+if not exist "%JRE_HOME%\bin\java.exe" goto noJreHome
+if not exist "%JRE_HOME%\bin\javaw.exe" goto noJreHome
+goto okJava
+
+:noJreHome
+rem Needed at least a JRE
+echo The JRE_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program
+goto exit
+
+:okJava
+rem Don't override the endorsed dir if the user has set it previously
+if not "%JAVA_ENDORSED_DIRS%" == "" goto gotEndorseddir
+rem Java 9 no longer supports the java.endorsed.dirs
+rem system property. Only try to use it if
+rem CATALINA_HOME/endorsed exists.
+if not exist "%CATALINA_HOME%\endorsed" goto gotEndorseddir
+set "JAVA_ENDORSED_DIRS=%CATALINA_HOME%\endorsed"
+:gotEndorseddir
+
+rem Don't override _RUNJAVA if the user has set it previously
+if not "%_RUNJAVA%" == "" goto gotRunJava
+rem Set standard command for invoking Java.
+rem Also note the quoting as JRE_HOME may contain spaces.
+set _RUNJAVA="%JRE_HOME%\bin\java.exe"
+:gotRunJava
+
+rem Don't override _RUNJDB if the user has set it previously
+rem Also note the quoting as JAVA_HOME may contain spaces.
+if not "%_RUNJDB%" == "" goto gotRunJdb
+set _RUNJDB="%JAVA_HOME%\bin\jdb.exe"
+:gotRunJdb
+if errorlevel 1 goto end
+
+rem Add on extra jar file to CLASSPATH
+rem Note that there are no quotes as we do not want to introduce random
+rem quotes into the CLASSPATH
+if "%CLASSPATH%" == "" goto emptyClasspath
+set "CLASSPATH=%CLASSPATH%;"
+:emptyClasspath
+set "CLASSPATH=%CLASSPATH%%WINEGROWER_HOME%\lib\*"
+
+if not "%WINEGROWER_TMPDIR%" == "" goto gotTmpdir
+set "WINEGROWER_TMPDIR=%WINEGROWER_BASE%\temp"
+:gotTmpdir
+
+if not "%JSSE_OPTS%" == "" goto gotJsseOpts
+set JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048"
+:gotJsseOpts
+set "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"
+
+if not "%LOGGING_CONFIG%" == "" goto noJuliConfig
+set LOGGING_CONFIG="-Dwinegrower.script.nologgingconfig"
+if not exist "%WINEGROWER_BASE%\conf\logging.properties" goto noJuliConfig
+set LOGGING_CONFIG=-Djava.util.logging.config.file="%WINEGROWER_BASE%\conf\logging.properties"
+:noJuliConfig
+
+if not "%LOGGING_MANAGER%" == "" goto noJuliManager
+set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
+:noJuliManager
+
+rem Configure JAVA 9 specific start-up parameters
+rem TODO: set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.lang=ALL-UNNAMED"
+rem TODO: set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
+
+rem ----- Execute The Requested Command ---------------------------------------
+
+echo Using WINEGROWER_BASE:   "%WINEGROWER_BASE%"
+echo Using WINEGROWER_HOME:   "%WINEGROWER_HOME%"
+echo Using WINEGROWER_TMPDIR: "%WINEGROWER_TMPDIR%"
+if ""%1"" == ""debug"" goto use_jdk
+echo Using JRE_HOME:        "%JRE_HOME%"
+goto java_dir_displayed
+:use_jdk
+echo Using JAVA_HOME:       "%JAVA_HOME%"
+:java_dir_displayed
+echo Using CLASSPATH:       "%CLASSPATH%"
+
+set _EXECJAVA=%_RUNJAVA%
+set MAINCLASS=${main}
+set ACTION=start
+set SECURITY_POLICY_FILE=
+set DEBUG_OPTS=
+set JPDA=
+
+if not ""%1"" == ""jpda"" goto noJpda
+set JPDA=jpda
+if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport
+set JPDA_TRANSPORT=dt_socket
+:gotJpdaTransport
+if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress
+set JPDA_ADDRESS=localhost:8000
+:gotJpdaAddress
+if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend
+set JPDA_SUSPEND=n
+:gotJpdaSuspend
+if not "%JPDA_OPTS%" == "" goto gotJpdaOpts
+set JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
+:gotJpdaOpts
+shift
+:noJpda
+
+if ""%1"" == ""debug"" goto doDebug
+if ""%1"" == ""run"" goto doRun
+if ""%1"" == ""start"" goto doStart
+if ""%1"" == ""stop"" goto doStop
+
+echo Usage:  WINEGROWER ( commands ... )
+echo commands:
+echo   run               Start WINEGROWER in the current window
+echo   start             Start WINEGROWER in a separate window
+echo   stop              Stop WINEGROWER
+goto end
+
+:doDebug
+shift
+set _EXECJAVA=%_RUNJDB%
+set DEBUG_OPTS=-sourcepath "%WINEGROWER_HOME%\..\..\java"
+if not ""%1"" == ""-security"" goto execCmd
+shift
+echo Using Security Manager
+set "SECURITY_POLICY_FILE=%WINEGROWER_BASE%\conf\WINEGROWER.policy"
+goto execCmd
+
+:doRun
+goto execCmd
+
+:doStart
+shift
+if "%TITLE%" == "" set TITLE=Winegrower
+set _EXECJAVA=start "%TITLE%" %_RUNJAVA%
+goto execCmd
+
+:doStop
+shift
+set ACTION=stop
+set WINEGROWER_OPTS=
+goto execCmd
+
+:execCmd
+rem Get remaining unshifted command line arguments and save them in the
+set CMD_LINE_ARGS=
+:setArgs
+if ""%1""=="""" goto doneSetArgs
+set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
+shift
+goto setArgs
+:doneSetArgs
+
+rem Execute Java with the applicable properties
+if not "%JPDA%" == "" goto doJpda
+if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity
+%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %WINEGROWER_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -DWINEGROWER.base="%WINEGROWER_BASE%" -DWINEGROWER.home="%WINEGROWER_HOME%" -Djava.io.tmpdir="%WINEGROWER_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
+goto end
+:doSecurity
+%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %WINEGROWER_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -DWINEGROWER.base="%WINEGROWER_BASE%" -DWINEGROWER.home="%WINEGROWER_HOME%" -Djava.io.tmpdir="%WINEGROWER_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
+goto end
+:doJpda
+if not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda
+%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %WINEGROWER_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -DWINEGROWER.base="%WINEGROWER_BASE%" -DWINEGROWER.home="%WINEGROWER_HOME%" -Djava.io.tmpdir="%WINEGROWER_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
+goto end
+:doSecurityJpda
+%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %WINEGROWER_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -DWINEGROWER.base="%WINEGROWER_BASE%" -DWINEGROWER.home="%WINEGROWER_HOME%" -Djava.io.tmpdir="%WINEGROWER_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
+goto end
+
+:end
\ No newline at end of file
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.sh b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.sh
new file mode 100644
index 0000000..e907c9d
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/main/resources/bin/winegrower.sh
@@ -0,0 +1,486 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# This script is forked from Apache Tomcat
+#
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+os400=false
+hpux=false
+case "`uname`" in
+CYGWIN*) cygwin=true;;
+Darwin*) darwin=true;;
+OS400*) os400=true;;
+HP-UX*) hpux=true;;
+esac
+
+# resolve links - $0 may be a softlink
+PRG="$0"
+
+while [ -h "$PRG" ]; do
+  ls=`ls -ld "$PRG"`
+  link=`expr "$ls" : '.*-> \(.*\)$'`
+  if expr "$link" : '/.*' > /dev/null; then
+    PRG="$link"
+  else
+    PRG=`dirname "$PRG"`/"$link"
+  fi
+done
+
+# Get standard environment variables
+PRGDIR=`dirname "$PRG"`
+
+# Only set WINEGROWER_HOME if not already set
+[ -z "$WINEGROWER_HOME" ] && WINEGROWER_HOME=`cd "$PRGDIR/.." >/dev/null; pwd`
+
+# Copy WINEGROWER_BASE from WINEGROWER_HOME if not already set
+[ -z "$WINEGROWER_BASE" ] && WINEGROWER_BASE="$WINEGROWER_HOME"
+
+# Ensure that any user defined CLASSPATH variables are not used on startup,
+# but allow them to be specified in setenv.sh, in rare case when it is needed.
+CLASSPATH=
+
+if [ -r "$WINEGROWER_BASE/bin/setenv.sh" ]; then
+  . "$WINEGROWER_BASE/bin/setenv.sh"
+elif [ -r "$WINEGROWER_HOME/bin/setenv.sh" ]; then
+  . "$WINEGROWER_HOME/bin/setenv.sh"
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+  [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$JRE_HOME" ] && JRE_HOME=`cygpath --unix "$JRE_HOME"`
+  [ -n "$WINEGROWER_HOME" ] && WINEGROWER_HOME=`cygpath --unix "$WINEGROWER_HOME"`
+  [ -n "$WINEGROWER_BASE" ] && WINEGROWER_BASE=`cygpath --unix "$WINEGROWER_BASE"`
+  [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# Ensure that neither WINEGROWER_HOME nor WINEGROWER_BASE contains a colon
+# as this is used as the separator in the classpath and Java provides no
+# mechanism for escaping if the same character appears in the path.
+case $WINEGROWER_HOME in
+  *:*) echo "Using WINEGROWER_HOME:   $WINEGROWER_HOME";
+       echo "Unable to start as WINEGROWER_HOME contains a colon (:) character";
+       exit 1;
+esac
+case $WINEGROWER_BASE in
+  *:*) echo "Using WINEGROWER_BASE:   $WINEGROWER_BASE";
+       echo "Unable to start as WINEGROWER_BASE contains a colon (:) character";
+       exit 1;
+esac
+
+# For OS400
+if $os400; then
+  # Set job priority to standard for interactive (interactive - 6) by using
+  # the interactive priority - 6, the helper threads that respond to requests
+  # will be running at the same priority as interactive jobs.
+  COMMAND='chgjob job('$JOBNAME') runpty(6)'
+  system $COMMAND
+
+  # Enable multi threading
+  export QIBM_MULTI_THREADED=Y
+fi
+
+# Get standard Java environment variables
+# Make sure prerequisite environment variables are set
+if [ -z "$JAVA_HOME" -a -z "$JRE_HOME" ]; then
+  if $darwin; then
+    # Bugzilla 54390
+    if [ -x '/usr/libexec/java_home' ] ; then
+      export JAVA_HOME=`/usr/libexec/java_home`
+    # Bugzilla 37284 (reviewed).
+    elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then
+      export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
+    fi
+  else
+    JAVA_PATH=`which java 2>/dev/null`
+    if [ "x$JAVA_PATH" != "x" ]; then
+      JAVA_PATH=`dirname $JAVA_PATH 2>/dev/null`
+      JRE_HOME=`dirname $JAVA_PATH 2>/dev/null`
+    fi
+    if [ "x$JRE_HOME" = "x" ]; then
+      # XXX: Should we try other locations?
+      if [ -x /usr/bin/java ]; then
+        JRE_HOME=/usr
+      fi
+    fi
+  fi
+  if [ -z "$JAVA_HOME" -a -z "$JRE_HOME" ]; then
+    echo "Neither the JAVA_HOME nor the JRE_HOME environment variable is defined"
+    echo "At least one of these environment variable is needed to run this program"
+    exit 1
+  fi
+fi
+if [ -z "$JAVA_HOME" -a "$1" = "debug" ]; then
+  echo "JAVA_HOME should point to a JDK in order to run in debug mode."
+  exit 1
+fi
+if [ -z "$JRE_HOME" ]; then
+  JRE_HOME="$JAVA_HOME"
+fi
+
+# If we're running under jdb, we need a full jdk.
+if [ "$1" = "debug" ] ; then
+  if [ "$os400" = "true" ]; then
+    if [ ! -x "$JAVA_HOME"/bin/java -o ! -x "$JAVA_HOME"/bin/javac ]; then
+      echo "The JAVA_HOME environment variable is not defined correctly"
+      echo "This environment variable is needed to run this program"
+      echo "NB: JAVA_HOME should point to a JDK not a JRE"
+      exit 1
+    fi
+  else
+    if [ ! -x "$JAVA_HOME"/bin/java -o ! -x "$JAVA_HOME"/bin/jdb -o ! -x "$JAVA_HOME"/bin/javac ]; then
+      echo "The JAVA_HOME environment variable is not defined correctly"
+      echo "This environment variable is needed to run this program"
+      echo "NB: JAVA_HOME should point to a JDK not a JRE"
+      exit 1
+    fi
+  fi
+fi
+
+# Set standard commands for invoking Java, if not already set.
+if [ -z "$_RUNJAVA" ]; then
+  _RUNJAVA="$JRE_HOME"/bin/java
+fi
+if [ "$os400" != "true" ]; then
+  if [ -z "$_RUNJDB" ]; then
+    _RUNJDB="$JAVA_HOME"/bin/jdb
+  fi
+fi
+
+# Add on extra jar files to CLASSPATH
+if [ ! -z "$CLASSPATH" ] ; then
+  CLASSPATH="$CLASSPATH":
+fi
+if [ "$WINEGROWER_HOME" != "$WINEGROWER_BASE" ]; then
+  for i in "$WINEGROWER_BASE/lib/"*.jar ; do
+    if [ -z "$CLASSPATH" ] ; then
+      CLASSPATH="$i"
+    else
+      CLASSPATH="$i:$CLASSPATH"
+    fi
+  done
+fi
+for i in "$WINEGROWER_HOME/lib/"*.jar ; do
+  if [ -z "$CLASSPATH" ] ; then
+    CLASSPATH="$i"
+  else
+    CLASSPATH="$i:$CLASSPATH"
+  fi
+done
+
+if [ -z "$WINEGROWER_OUT" ] ; then
+  WINEGROWER_OUT="$WINEGROWER_BASE"/logs/winegrower.out
+fi
+
+if [ -z "$WINEGROWER_TMPDIR" ] ; then
+  # Define the java.io.tmpdir to use for WINEGROWER
+  WINEGROWER_TMPDIR="$WINEGROWER_BASE"/temp
+fi
+
+# Bugzilla 37848: When no TTY is available, don't output to console
+have_tty=0
+if [ "`tty`" != "not a tty" ]; then
+    have_tty=1
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  JAVA_HOME=`cygpath --absolute --windows "$JAVA_HOME"`
+  JRE_HOME=`cygpath --absolute --windows "$JRE_HOME"`
+  WINEGROWER_HOME=`cygpath --absolute --windows "$WINEGROWER_HOME"`
+  WINEGROWER_BASE=`cygpath --absolute --windows "$WINEGROWER_BASE"`
+  WINEGROWER_TMPDIR=`cygpath --absolute --windows "$WINEGROWER_TMPDIR"`
+  CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+fi
+
+if [ -z "$JSSE_OPTS" ] ; then
+  JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048"
+fi
+JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS"
+
+# Set juli LogManager config file if it is present and an override has not been issued
+if [ -z "$LOGGING_CONFIG" ]; then
+  if [ -r "$WINEGROWER_BASE"/conf/logging.properties ]; then
+    LOGGING_CONFIG="-Djava.util.logging.config.file=$WINEGROWER_BASE/conf/logging.properties"
+  else
+    # Bugzilla 45585
+    LOGGING_CONFIG="-Dwinegrower.script.nologgingconfig"
+  fi
+fi
+
+if [ -z "$LOGGING_MANAGER" ]; then
+  LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"
+fi
+
+# Set UMASK unless it has been overridden
+if [ -z "$UMASK" ]; then
+    UMASK="0027"
+fi
+umask $UMASK
+
+# Uncomment the following line to make the umask available when using the
+# org.apache.tomcat.security.SecurityListener
+#JAVA_OPTS="$JAVA_OPTS -Dorg.apache.tomcat.security.SecurityListener.UMASK=`umask`"
+
+if [ -z "$USE_NOHUP" ]; then
+    if $hpux; then
+        USE_NOHUP="true"
+    else
+        USE_NOHUP="false"
+    fi
+fi
+unset _NOHUP
+if [ "$USE_NOHUP" = "true" ]; then
+    _NOHUP=nohup
+fi
+
+if [ -z "$WINEGROWER_PID" ]; then
+  WINEGROWER_PID="$WINEGROWER_BASE"/conf/winegrower.pid
+fi
+# ----- Execute The Requested Command -----------------------------------------
+
+# Bugzilla 37848: only output this if we have a TTY
+if [ $have_tty -eq 1 ]; then
+  echo "Using WINEGROWER_BASE:   $WINEGROWER_BASE"
+  echo "Using WINEGROWER_HOME:   $WINEGROWER_HOME"
+  echo "Using WINEGROWER_TMPDIR: $WINEGROWER_TMPDIR"
+  echo "Using JRE_HOME:        $JRE_HOME"
+  echo "Using CLASSPATH:       $CLASSPATH"
+  if [ ! -z "$WINEGROWER_PID" ]; then
+    echo "Using WINEGROWER_PID:    $WINEGROWER_PID"
+  fi
+fi
+
+if [ "$1" = "jpda" ] ; then
+  if [ -z "$JPDA_TRANSPORT" ]; then
+    JPDA_TRANSPORT="dt_socket"
+  fi
+  if [ -z "$JPDA_ADDRESS" ]; then
+    JPDA_ADDRESS="localhost:8000"
+  fi
+  if [ -z "$JPDA_SUSPEND" ]; then
+    JPDA_SUSPEND="n"
+  fi
+  if [ -z "$JPDA_OPTS" ]; then
+    JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,server=y,address=$JPDA_ADDRESS,suspend=$JPDA_SUSPEND"
+  fi
+  WINEGROWER_OPTS="$JPDA_OPTS $WINEGROWER_OPTS"
+  shift
+fi
+
+WINEGROWER_LOG4J2_PATH="$WINEGROWER_BASE"/conf/log4j2.xml
+if [ -f "$WINEGROWER_LOG4J2_PATH" ]; then
+  if $cygwin; then
+    WINEGROWER_LOG4J2_PATH=`cygpath --absolute --windows "$WINEGROWER_LOG4J2_PATH"`
+  fi
+  WINEGROWER_OPTS="$WINEGROWER_OPTS "-Dlog4j.configurationFile=\"$WINEGROWER_LOG4J2_PATH\"""
+fi
+
+if [ "$1" = "run" ]; then
+
+  shift
+  eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $WINEGROWER_OPTS \
+    -classpath "\"$CLASSPATH\"" \
+    -Dwinegrower.base="\"$WINEGROWER_BASE\"" \
+    -Dwinegrower.home="\"$WINEGROWER_HOME\"" \
+    -Dkaraf.home="\"$WINEGROWER_HOME\"" \
+    -Dkaraf.base="\"$WINEGROWER_BASE\"" \
+    -Djava.io.tmpdir="\"$WINEGROWER_TMPDIR\"" \
+    ${main} "$WINEGROWER_ARGS" "$@"
+
+elif [ "$1" = "start" ] ; then
+
+  if [ ! -z "$WINEGROWER_PID" ]; then
+    if [ -f "$WINEGROWER_PID" ]; then
+      if [ -s "$WINEGROWER_PID" ]; then
+        echo "Existing PID file found during start."
+        if [ -r "$WINEGROWER_PID" ]; then
+          PID=`cat "$WINEGROWER_PID"`
+          ps -p $PID >/dev/null 2>&1
+          if [ $? -eq 0 ] ; then
+            echo "Winegrower appears to still be running with PID $PID. Start aborted."
+            echo "If the following process is not a Winegrower process, remove the PID file and try again:"
+            ps -f -p $PID
+            exit 1
+          else
+            echo "Removing/clearing stale PID file."
+            rm -f "$WINEGROWER_PID" >/dev/null 2>&1
+            if [ $? != 0 ]; then
+              if [ -w "$WINEGROWER_PID" ]; then
+                cat /dev/null > "$WINEGROWER_PID"
+              else
+                echo "Unable to remove or clear stale PID file. Start aborted."
+                exit 1
+              fi
+            fi
+          fi
+        else
+          echo "Unable to read PID file. Start aborted."
+          exit 1
+        fi
+      else
+        rm -f "$WINEGROWER_PID" >/dev/null 2>&1
+        if [ $? != 0 ]; then
+          if [ ! -w "$WINEGROWER_PID" ]; then
+            echo "Unable to remove or write to empty PID file. Start aborted."
+            exit 1
+          fi
+        fi
+      fi
+    fi
+  fi
+
+  shift
+  touch "$WINEGROWER_OUT"
+  eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $WINEGROWER_OPTS \
+    -classpath "\"$CLASSPATH\"" \
+    -Dwinegrower.base="\"$WINEGROWER_BASE\"" \
+    -Dwinegrower.home="\"$WINEGROWER_HOME\"" \
+    -Djava.io.tmpdir="\"$WINEGROWER_TMPDIR\"" \
+    ${main} "$WINEGROWER_ARGS" "$@" \
+    >> "$WINEGROWER_OUT" 2>&1 "&"
+
+  if [ ! -z "$WINEGROWER_PID" ]; then
+    echo $! > "$WINEGROWER_PID"
+  fi
+
+  echo "Winegrower started."
+
+elif [ "$1" = "stop" ] ; then
+
+  shift
+
+  SLEEP=15
+  if [ ! -z "$1" ]; then
+    echo $1 | grep "[^0-9]" >/dev/null 2>&1
+    if [ $? -gt 0 ]; then
+      SLEEP=$1
+      shift
+    fi
+  fi
+
+  FORCE=0
+  if [ "$1" = "-force" ]; then
+    shift
+    FORCE=1
+  fi
+
+  if [ ! -z "$WINEGROWER_PID" ]; then
+    if [ -f "$WINEGROWER_PID" ]; then
+      if [ -s "$WINEGROWER_PID" ]; then
+        kill -15 `cat "$WINEGROWER_PID"` >/dev/null 2>&1
+        if [ $? -gt 0 ]; then
+          echo "PID file found but no matching process was found. Stop aborted."
+          exit 1
+        fi
+      else
+        echo "PID file is empty and has been ignored."
+      fi
+    else
+      echo "\$WINEGROWER_PID was set but the specified file does not exist. Is Winegrower running? Stop aborted."
+      exit 1
+    fi
+  fi
+
+  if [ ! -z "$WINEGROWER_PID" ]; then
+    if [ -f "$WINEGROWER_PID" ]; then
+      while [ $SLEEP -ge 0 ]; do
+        kill -15 `cat "$WINEGROWER_PID"` >/dev/null 2>&1
+        if [ $? -gt 0 ]; then
+          rm -f "$WINEGROWER_PID" >/dev/null 2>&1
+          if [ $? != 0 ]; then
+            if [ -w "$WINEGROWER_PID" ]; then
+              cat /dev/null > "$WINEGROWER_PID"
+              # If Winegrower has stopped don't try and force a stop with an empty PID file
+              FORCE=0
+            else
+              echo "The PID file could not be removed or cleared."
+            fi
+          fi
+          echo "Winegrower stopped."
+          break
+        fi
+        if [ $SLEEP -gt 0 ]; then
+          sleep 1
+        fi
+        if [ $SLEEP -eq 0 ]; then
+          echo "Winegrower did not stop in time."
+          if [ $FORCE -eq 0 ]; then
+            echo "PID file was not removed."
+          fi
+          echo "To aid diagnostics a thread dump has been written to standard out."
+          kill -3 `cat "$WINEGROWER_PID"`
+        fi
+        SLEEP=`expr $SLEEP - 1 `
+      done
+    fi
+  fi
+
+  KILL_SLEEP_INTERVAL=15
+  if [ $FORCE -eq 1 ]; then
+    if [ -z "$WINEGROWER_PID" ]; then
+      echo "Kill failed: \$WINEGROWER_PID not set"
+    else
+      if [ -f "$WINEGROWER_PID" ]; then
+        PID=`cat "$WINEGROWER_PID"`
+        echo "Killing Winegrower with the PID: $PID"
+        kill -9 $PID
+        while [ $KILL_SLEEP_INTERVAL -ge 0 ]; do
+            kill -0 `cat "$WINEGROWER_PID"` >/dev/null 2>&1
+            if [ $? -gt 0 ]; then
+                rm -f "$WINEGROWER_PID" >/dev/null 2>&1
+                if [ $? != 0 ]; then
+                    if [ -w "$WINEGROWER_PID" ]; then
+                        cat /dev/null > "$WINEGROWER_PID"
+                    else
+                        echo "The PID file could not be removed."
+                    fi
+                fi
+                echo "The Winegrower process has been killed."
+                break
+            fi
+            if [ $KILL_SLEEP_INTERVAL -gt 0 ]; then
+                sleep 1
+            fi
+            KILL_SLEEP_INTERVAL=`expr $KILL_SLEEP_INTERVAL - 1 `
+        done
+        if [ $KILL_SLEEP_INTERVAL -lt 0 ]; then
+            echo "Winegrower has not been killed completely yet. The process might be waiting on some system call or might be UNINTERRUPTIBLE."
+        fi
+      fi
+    fi
+  fi
+
+else
+
+  echo "Usage: WINEGROWER.sh ( commands ... )"
+  echo "commands:"
+  echo "  jpda start        Start WINEGROWER under JPDA debugger"
+  echo "  run               Start WINEGROWER in the current window"
+  echo "  start             Start WINEGROWER in a separate window"
+  echo "  stop              Stop WINEGROWER, waiting up t/o 15 seconds for the process to end"
+  echo "  stop n            Stop WINEGROWER, waiting up to n seconds for the process to end"
+  echo "  stop -force       Stop WINEGROWER, wait up to 15 seconds and then use kill -KILL if still running"
+  echo "  stop n -force     Stop WINEGROWER, wait up to n seconds and then use kill -KILL if still running"
+  echo "Note: Waiting for the process to end and use of the -force option require that \$WINEGROWER_PID is defined"
+  exit 1
+
+fi
\ No newline at end of file
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/BuildTest.java b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/BuildTest.java
new file mode 100644
index 0000000..47e1f21
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/BuildTest.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.common;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.list;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static org.apache.xbean.finder.util.Files.toFile;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.winegrower.Ripener;
+import org.junit.jupiter.api.Test;
+import org.osgi.framework.Bundle;
+
+class BuildTest {
+    @Test
+    void build() throws IOException {
+        final String junitMarker = Test.class.getName().replace('.', '/') + ".class";
+        final File junitApi = toFile(Thread.currentThread().getContextClassLoader().getResource(junitMarker));
+        final String osgiCoreMarker = Bundle.class.getName().replace('.', '/') + ".class";
+        final File osgiCore = toFile(Thread.currentThread().getContextClassLoader().getResource(osgiCoreMarker));
+
+        final File output = new File("target/buildtest/build.jar");
+        if (output.exists()) {
+            output.delete();
+        }
+        final File workDir = new File(output.getParentFile(), "work");
+        new Build(new Build.Configuration(
+                workDir,
+                new File("src/test/resources/build"),
+                "test-art",
+                asList(junitApi, osgiCore),
+                singletonList("zip"),
+                Ripener.class.getName(),
+                "bin", "conf", false, false
+        )).run();
+        final File distro = new File(workDir, "test-art-winegrower-distribution.zip");
+        assertTrue(distro.exists());
+        final List<String> entries;
+        try (final JarFile files = new JarFile(distro)) {
+            entries = list(files.entries()).stream().map(JarEntry::getName).collect(toList());
+        }
+        assertEquals(11, entries.size());
+        assertTrue(entries.contains("test-art-winegrower-distribution/bin/winegrower.sh"));
+        assertTrue(entries.contains("test-art-winegrower-distribution/bin/setenv.sh"));
+        assertTrue(entries.contains("test-art-winegrower-distribution/lib/org.osgi.core-6.0.0.jar"));
+        assertTrue(entries.contains("test-art-winegrower-distribution/lib/junit-jupiter-api-5.3.1.jar"));
+    }
+}
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/FatJarTest.java b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/FatJarTest.java
new file mode 100644
index 0000000..b41c05b
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/java/org/apache/winegrower/extension/build/common/FatJarTest.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.common;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.list;
+import static java.util.stream.Collectors.toList;
+import static org.apache.xbean.finder.util.Files.toFile;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.junit.jupiter.api.Test;
+import org.osgi.framework.Bundle;
+
+class FatJarTest {
+    @Test
+    void fatJar() throws IOException {
+        final String junitMarker = Test.class.getName().replace('.', '/') + ".class";
+        final File junitApi = toFile(Thread.currentThread().getContextClassLoader().getResource(junitMarker));
+        final String osgiCoreMarker = Bundle.class.getName().replace('.', '/') + ".class";
+        final File osgiCore = toFile(Thread.currentThread().getContextClassLoader().getResource(osgiCoreMarker));
+
+        final File output = new File("target/farjartest/fatJar.jar");
+        if (output.exists()) {
+            output.delete();
+        }
+        new FatJar(new FatJar.Configuration(
+                asList(junitApi, osgiCore),
+                output
+        )).run();
+        assertTrue(output.exists());
+        final List<String> entries;
+        try (final JarFile files = new JarFile(output)) {
+            entries = list(files.entries()).stream().map(JarEntry::getName).collect(toList());
+        }
+        assertTrue(entries.size() > 500); // 503 when writing this test
+        // ensure junit and osgi-core are here by testing a few known classes
+        assertTrue(entries.contains("org/junit/jupiter/api/AfterAll.class"));
+        assertTrue(entries.contains("org/osgi/framework/FrameworkUtil.class"));
+        // ensure fatjar meta are here
+        assertTrue(entries.contains("WINEGROWER-INF/index.properties"));
+        assertTrue(entries.contains("WINEGROWER-INF/manifests.properties"));
+    }
+}
diff --git a/winegrower-extension/winegrower-build/winegrower-build-common/src/test/resources/build/bin/setenv.sh b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/resources/build/bin/setenv.sh
new file mode 100644
index 0000000..81f90a2
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-build-common/src/test/resources/build/bin/setenv.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+echo "Starting"
diff --git a/winegrower-extension/winegrower-build/winegrower-maven-plugin/pom.xml b/winegrower-extension/winegrower-build/winegrower-maven-plugin/pom.xml
index 72a214c..be2e44e 100644
--- a/winegrower-extension/winegrower-build/winegrower-maven-plugin/pom.xml
+++ b/winegrower-extension/winegrower-build/winegrower-maven-plugin/pom.xml
@@ -59,6 +59,11 @@
       <artifactId>winegrower-build-common</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-dependency-tree</artifactId>
+      <version>3.0.1</version>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/asciidoc/index.adoc b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/asciidoc/index.adoc
index c6fc818..12df619 100644
--- a/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/asciidoc/index.adoc
+++ b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/asciidoc/index.adoc
@@ -1,5 +1,20 @@
 = Apache Winegrower Maven Plugin
 
+Apache Winegrower Maven plugin provides the elementary tasks to develop and package your application depending your need.
+
+Here is how to declare it:
+
+[source,xml]
+----
+<plugin>
+  <groupId>org.apache.winegrower</groupId>
+  <artifactId>winegrower-maven-plugin</artifactId>
+  <version>${winegrower.version}</version>
+</plugin>
+----
+
+== Start a dev instance (a.k.a. `Pour`)
+
 Apache Winegrower Maven plugin allows to start an instance very easily thanks to `pour` command:
 
 [source,sh]
@@ -7,7 +22,7 @@
 mvn winegrower:pour
 ----
 
-== Configuration
+=== Configuration
 
 [cols="e,m,m,m,a",headers]
 |===
@@ -22,7 +37,7 @@
 |waitOnSystemIn|boolean|winegrower.waitOnSystemIn|${project.build.directory}/winegrower/workdir|should the mojo wait you enter any character to exist, it is useful to disable it if you use the Karaf shell which reads `System.in` as well.
 |===
 
-== Example
+=== Example
 
 [source,xml]
 ----
@@ -37,4 +52,71 @@
     <waitOnSystemIn>false</waitOnSystemIn> <!-- cause of the shell -->
   </configuration>
 </plugin>
-----
\ No newline at end of file
+----
+
+== Create a fatjar
+
+Creating a fatjar is a common need. For OSGi it becomes quite an issue
+since you need to keep each `MANIFEST.MF` as it since some values can't become plural.
+This is the main blocker making the `maven-shade-plugin` and `maven-assembly-plugin` quite helpless
+on that task.
+
+To solve that, Winegrower provides a `fatjar` goal which will generate an index of each shaded jars
+and keep their manifests as it and the runtime knows how to read them back.
+
+[source,sh]
+----
+mvn winegrower:fatjar
+----
+
+=== Configuration
+
+[cols="e,m,m,m,a",headers]
+|===
+|Name|Type|Property|Default|Description
+|attach|boolean|winegrower.attach|true|should the built jar be attached (installed)
+|buildArtifact|File|winegrower.buildArtifact|${project.build.directory}/${project.build.finalName}.${project.packaging}.|Path of the project artifact if any.
+|classifier|String|winegrower.classifier|fatjar|The classifier to use if the produced artifact is attached.
+|includeScopes|Collection<String>|winegrower.includeScopes|provided,compile,runtime|The scopes included in the produced artifact.
+|output|File|winegrower.output|${project.build.directory}/${project.artifactId}-fatjar.jar|Where the fatjar is produced.
+|===
+
+== Create a distribution
+
+Since Winegrower does not require an OSGi runtime anymore, it also provide a `distribution` goal
+which will bundle your application as a standard java application with a `lib/` folder
+and scripts in `bin` to start/stop the application.
+
+The scripts are highly inspired from Apache Tomcat ones so if you are familiar with this server
+you shouldn't be lost.
+
+[source,sh]
+----
+mvn winegrower:distribution
+----
+
+Then once the distributino unzipped you can start using:
+
+[source,sh]
+----
+./bin/wingrower.sh run
+----
+
+=== Configuration
+
+[cols="e,m,m,m,a",headers]
+|===
+|Name|Type|Property|Default|Description
+|attach|boolean|winegrower.attach|true|should the built distributions be attached (installed)
+|buildArtifact|File|winegrower.buildArtifact|${project.build.directory}/${project.build.finalName}.${project.packaging}.|Path of the project artifact if any.
+|classifier|String|winegrower.classifier|fatjar-%s.|The classifier to use if the produced artifact is attached. Note it is a pattern (`String.format`) taking the format as parameter.
+|includeScopes|Collection<String>|winegrower.includeScopes|provided,compile,runtime|The scopes included in the produced artifact.
+|conf|String|winegrower.conf|src/main/winegrower/conf|Path synchronized with the distribution conf folder.
+|bin|String|winegrower.bin|src/main/winegrower/bin|Path synchronized with the distribution bin folder.
+|formats|Collection<String>|winegrower.formats|zip|Distribution formats, `zip` and `tar.gz` are supported.
+|keepExplodedFolder|boolean|winegrower.keepExplodedFolder|false|Should the distribution work directory be kept in the build directory.
+|libs|Collection<String>|winegrower.libs|-|List of maven coordinates (`group:artifact:version[?transitive]`) to include in the distribution even if not visible as dependency.
+|main|String|winegrower.main|org.apache.winegrower.Ripener|The main to run when starting the distribution.
+|skipArchiveRootFolder|boolean|winegrower.skipArchiveRootFolder|false|Should the distribution keep a root folder.
+|workDir|File|winegrower.workDir|${project.build.directory}/${project.artifactId}-distribution|Where the distribution is built during the build.
+|===
diff --git a/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/DistributionMojo.java b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/DistributionMojo.java
new file mode 100644
index 0000000..b5890f9
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/DistributionMojo.java
@@ -0,0 +1,217 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.maven;
+
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+import static org.apache.maven.plugins.annotations.ResolutionScope.RUNTIME_PLUS_SYSTEM;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.DefaultDependencyResolutionRequest;
+import org.apache.maven.project.DependencyResolutionException;
+import org.apache.maven.project.DependencyResolutionRequest;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.maven.project.ProjectDependenciesResolver;
+import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
+import org.apache.winegrower.extension.build.common.Build;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.impl.ArtifactResolver;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+@Mojo(name = "distribution", requiresDependencyResolution = RUNTIME_PLUS_SYSTEM)
+public class DistributionMojo extends AbstractMojo {
+    @Parameter(defaultValue = "${project}", readonly = true, required = true)
+    private MavenProject project;
+
+    @Parameter(defaultValue = "provided,compile,runtime", property = "winegrower.includeScopes")
+    private Collection<String> includeScopes;
+
+    @Parameter(defaultValue = "${project.build.directory}/${project.artifactId}-distribution", property = "winegrower.workDir")
+    private File workDir;
+
+    @Parameter(defaultValue = "zip", property = "winegrower.formats")
+    private Collection<String> formats;
+
+    @Parameter(defaultValue = "org.apache.winegrower.Ripener", property = "winegrower.main")
+    private String main;
+
+    @Parameter(property = "winegrower.bin", defaultValue = "src/main/winegrower/bin")
+    private String bin;
+
+    @Parameter(property = "winegrower.conf", defaultValue = "src/main/winegrower/conf")
+    private String conf;
+
+    @Parameter(property = "winegrower.libs")
+    private Collection<String> libs;
+
+    @Parameter(property = "winegrower.no-root", defaultValue = "false")
+    private boolean skipArchiveRootFolder;
+
+    @Parameter(property = "winegrower.keep-exploded-folder", defaultValue = "false")
+    private boolean keepExplodedFolder;
+
+    @Parameter(defaultValue = "true", property = "winegrower.attach")
+    private boolean attach;
+
+    @Parameter(defaultValue = "fatjar-%s", property = "winegrower.classifier")
+    private String classifier;
+
+    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.${project.packaging}", property = "winegrower.buildArtifact")
+    private File buildArtifact;
+
+    @Component
+    private MavenProjectHelper helper;
+
+    @Component
+    private ArtifactResolver resolver;
+
+    @Component
+    private RepositorySystem repositorySystem;
+
+    @Component
+    private ProjectDependenciesResolver dependenciesResolver;
+
+    @Component
+    private DependencyGraphBuilder graphBuilder;
+
+    @Parameter(defaultValue = "${repositorySystemSession}")
+    private RepositorySystemSession session;
+
+    @Parameter(defaultValue = "${project.remoteProjectRepositories}")
+    private List<RemoteRepository> remoteRepositories;
+
+    @Override
+    public void execute() {
+        new Build(new Build.Configuration(
+                workDir, project.getBasedir(), project.getArtifactId(),
+                collectJars(), formats,
+                main, bin, conf, skipArchiveRootFolder, keepExplodedFolder
+        )).run();
+        if (attach) {
+            formats.forEach(ext -> helper.attachArtifact(
+                    project,
+                    new File(workDir, project.getArtifactId() + "-winegrower-distribution." + ext),
+                    String.format(classifier, ext)));
+        }
+    }
+
+    private Stream<File> collectTransitiveDependencies(final Dependency dependency) {
+        final DependencyResolutionRequest request = new DefaultDependencyResolutionRequest();
+        request.setMavenProject(new MavenProject() {{
+            getDependencies().add(dependency);
+        }});
+        request.setRepositorySession(session);
+        try {
+            final Collection<File> files = new ArrayList<>();
+            dependenciesResolver.resolve(request).getDependencyGraph().accept(new DependencyVisitor() {
+                @Override
+                public boolean visitEnter(final DependencyNode node) {
+                    return true;
+                }
+
+                @Override
+                public boolean visitLeave(final DependencyNode node) {
+                    final org.eclipse.aether.artifact.Artifact artifact = node.getArtifact();
+                    files.add(artifact.getFile());
+                    return true;
+                }
+            });
+            return files.stream();
+        } catch (final DependencyResolutionException e) {
+            throw new IllegalStateException(e.getMessage(), e);
+        }
+    }
+
+    private File resolve(final String group, final String artifact, final String version, final String classifier) {
+        final DefaultArtifact art = new DefaultArtifact(group, artifact, classifier, "jar", version);
+        final ArtifactRequest artifactRequest = new ArtifactRequest().setArtifact(art).setRepositories(remoteRepositories);
+
+        final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
+        art.setFile(new File(lrm.getRepository().getBasedir(), lrm.getPathForLocalArtifact(artifactRequest.getArtifact())));
+
+        try {
+            final ArtifactResult result = repositorySystem.resolveArtifact(session, artifactRequest);
+            if (result.isMissing()) {
+                throw new IllegalStateException("Can't find commons-cli, please add it to the pom.");
+            }
+            return result.getArtifact().getFile();
+        } catch (final ArtifactResolutionException e) {
+            throw new IllegalStateException(e.getMessage(), e);
+        }
+    }
+
+    private Stream<File> collectLibs() {
+        return ofNullable(libs)
+                .map(value -> value.stream().flatMap(l -> {
+                    final boolean transitive = l.endsWith("?transitive");
+                    final String coords = transitive ? l.substring(0, l.length() - "?transitive".length()) : l;
+                    final String[] c = coords.split(":");
+                    if (c.length < 3 || c.length > 5) {
+                        throw new IllegalArgumentException("libs syntax is groupId:artifactId:version[:classifier][:type[?transitive]]");
+                    }
+                    if (!transitive) {
+                        return Stream.of(resolve(c[0], c[1], c[2], c.length == 4 ? c[3] : ""));
+                    } else {
+                        return collectTransitiveDependencies(new Dependency() {{
+                            setGroupId(c[0]);
+                            setArtifactId(c[1]);
+                            setVersion(c[2]);
+                            if (c.length == 4 && !"-".equals(c[3])) {
+                                setClassifier(c[3]);
+                            }
+                            if (c.length == 5) {
+                                setType(c[4]);
+                            }
+                        }});
+                    }
+                }).filter(it -> !it.getName().endsWith(".pom")))
+                .orElseGet(Stream::empty);
+    }
+    private Collection<File> collectJars() {
+        return Stream.concat(Stream.concat(
+                    collectDependencies(),
+                    Stream.of(buildArtifact)),
+                    collectLibs())
+                .filter(Objects::nonNull)
+                .filter(File::exists)
+                .collect(toList());
+    }
+
+    private Stream<File> collectDependencies() {
+        return project.getArtifacts().stream()
+            .filter(it -> includeScopes.contains(it.getScope()))
+            .map(Artifact::getFile);
+    }
+}
diff --git a/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/FatJarMojo.java b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/FatJarMojo.java
new file mode 100644
index 0000000..f06b11a
--- /dev/null
+++ b/winegrower-extension/winegrower-build/winegrower-maven-plugin/src/main/java/org/apache/winegrower/extension/build/maven/FatJarMojo.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.winegrower.extension.build.maven;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.maven.plugins.annotations.ResolutionScope.RUNTIME_PLUS_SYSTEM;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.winegrower.extension.build.common.FatJar;
+
+@Mojo(name = "fatjar", requiresDependencyResolution = RUNTIME_PLUS_SYSTEM)
+public class FatJarMojo extends AbstractMojo {
+    @Parameter(defaultValue = "${project}", readonly = true, required = true)
+    private MavenProject project;
+
+    @Parameter(defaultValue = "provided,compile,runtime", property = "winegrower.includeScopes")
+    private Collection<String> includeScopes;
+
+    @Parameter(defaultValue = "${project.build.directory}/${project.artifactId}-fatjar.jar", property = "winegrower.output")
+    private File output;
+
+    @Parameter(defaultValue = "true", property = "winegrower.attach")
+    private boolean attach;
+
+    @Parameter(defaultValue = "fatjar", property = "winegrower.classifier")
+    private String classifier;
+
+    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.${project.packaging}", property = "winegrower.buildArtifact")
+    private File buildArtifact;
+
+    @Component
+    private MavenProjectHelper helper;
+
+    @Override
+    public void execute() {
+        new FatJar(new FatJar.Configuration(collectJars(), output)).run();
+        if (attach) {
+            helper.attachArtifact(project, output, classifier);
+        }
+    }
+
+    private Collection<File> collectJars() {
+        return Stream.concat(
+                    project.getArtifacts().stream()
+                        .filter(it -> includeScopes.contains(it.getScope()))
+                        .map(Artifact::getFile),
+                    Stream.of(buildArtifact))
+                .filter(Objects::nonNull)
+                .filter(File::exists)
+                .collect(toList());
+    }
+}