diff --git a/app/src/main/java/de/ccc/events/badge/card10/hatchery/AppDetailFragment.kt b/app/src/main/java/de/ccc/events/badge/card10/hatchery/AppDetailFragment.kt
index 79d57e3af47ebdb2dc53ba7727e2cf1f2aa89861..f1926c94d04ef244df6c07ecb89720976cdedf10 100644
--- a/app/src/main/java/de/ccc/events/badge/card10/hatchery/AppDetailFragment.kt
+++ b/app/src/main/java/de/ccc/events/badge/card10/hatchery/AppDetailFragment.kt
@@ -22,7 +22,6 @@
 
 package de.ccc.events.badge.card10.hatchery
 
-import android.content.DialogInterface
 import android.os.AsyncTask
 import android.os.Bundle
 import android.util.Log
@@ -34,7 +33,6 @@ import androidx.fragment.app.Fragment
 import de.ccc.events.badge.card10.R
 import de.ccc.events.badge.card10.common.LoadingDialog
 import kotlinx.android.synthetic.main.app_detail_fragment.*
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry
 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
 import java.io.File
@@ -90,6 +88,7 @@ class AppDetailFragment : Fragment() {
             return try {
                 cacheDir.deleteRecursively()
                 cacheDir.mkdir()
+                val appDir = File(cacheDir.absolutePath + "/apps").mkdirs()
 
                 val inputStream = HatcheryClient().openDownloadStream(app)
                 val file = File.createTempFile(app.slug, ".tar.gz", cacheDir)
@@ -97,7 +96,7 @@ class AppDetailFragment : Fragment() {
 
                 inputStream.copyTo(outputStream)
 
-                val unpackedFiles = mutableListOf<String>()
+                val appFiles = mutableListOf<String>()
                 val tarStream = TarArchiveInputStream(GzipCompressorInputStream(file.inputStream()))
                 while (true) {
                     val entry = tarStream.nextTarEntry ?: break
@@ -106,15 +105,18 @@ class AppDetailFragment : Fragment() {
                     }
 
                     // TODO: A bit hacky. Maybe there is a better way?
-                    val targetFile = File(cacheDir, entry.name)
+                    val targetFile = File(cacheDir, "apps/${entry.name}")
                     targetFile.parentFile?.mkdirs()
                     targetFile.createNewFile()
                     Log.d(TAG, "Extracting ${entry.name} to ${targetFile.absolutePath}")
                     tarStream.copyTo(targetFile.outputStream())
-                    unpackedFiles.add(entry.name)
+                    appFiles.add("apps/${entry.name}")
                 }
 
-                unpackedFiles
+                val launcher = createLauncher(app.slug, cacheDir)
+                appFiles.add(launcher)
+
+                appFiles
             } catch (e: Exception) {
                 null
             }
@@ -129,5 +131,21 @@ class AppDetailFragment : Fragment() {
 
             loadingDialog.dismiss()
         }
+
+        fun createLauncher(slug: String, cacheDir: File): String {
+            val fileName = "$slug.py"
+            val file = File(cacheDir, fileName)
+            file.createNewFile()
+
+            val src = """
+                # Launcher script for $slug
+                import os
+                os.exec("apps/$slug/__init__.py")
+            """.trimIndent()
+
+            file.writeText(src)
+
+            return fileName
+        }
     }
 }