diff --git a/.gitignore b/.gitignore
index a72e0bd5bcd8634022c431725065d542a5d1d087..e8a873187121a803de6f8ace362389e28b7b8d74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *.App
+*.AppImage
 *.[oadi]
 *.ass
 *.avi
@@ -36,6 +37,7 @@
 *.vsp
 *.y4m
 *.zip
+*.DirIcon
 *~
 
 /bin
@@ -66,6 +68,7 @@ packages/desktop/aegisub.desktop
 packages/desktop/aegisub.desktop.template
 packages/win_installer/vendor
 src/aegisub
+Aegisub/
 src/libresrc/bitmap.cpp
 src/libresrc/bitmap.h
 src/libresrc/default_config.cpp
@@ -75,6 +78,7 @@ svn-revision.h
 svn_revision
 tests/*.json
 tests/run
+tools/linuxdeploy
 tools/osx-bundle-restart-helper
 tools/osx-bundle.sed
 tools/repack-thes-dict
@@ -98,5 +102,3 @@ vendor/luajit/src/luajit
 *.tmp
 cscope.files
 tags
-config.guess
-config.sub
diff --git a/Makefile.inc.in b/Makefile.inc.in
index be9a686ff7ac642c3991953f269f830336c73e7a..62178ddab1d7ac13e217348e78ce5a39d81e8e0c 100644
--- a/Makefile.inc.in
+++ b/Makefile.inc.in
@@ -21,6 +21,7 @@ LIB := $(LIB)
 # PLATFORM SETTINGS
 ###################
 BUILD_DARWIN  = @build_darwin@
+BUILD_LINUX   = @build_linux@
 
 #######
 # FLAGS
@@ -118,23 +119,29 @@ PRECOMPILED_HEADER = @enable_gcc_prec@
 ##########
 # BINARIES
 ##########
-BIN_AR       = ar
-BIN_RANLIB   = ranlib
-BIN_LN       = ln
-BIN_RM       = rm
-BIN_SHELL    = @SHELL@
-BIN_MV       = mv
-BIN_SED      = sed
-BIN_INSTALL  = @INSTALL@
-BIN_MSGMERGE = @MSGMERGE@
-BIN_XGETTEXT = @XGETTEXT@
-BIN_MSGFMT   = @MSGFMT@
-BIN_CC       = @CC@
-BIN_CXX      = @CXX@
-BIN_CP       = cp
-BIN_MKDIR    = mkdir
-BIN_MKDIR_P  = mkdir -p
-BIN_ECHO     = echo
-BIN_TOUCH    = touch
-BIN_LUA      = @LUA@
+BIN_AR        = ar
+BIN_RANLIB    = ranlib
+BIN_LN        = ln
+BIN_RM        = rm
+BIN_SHELL     = @SHELL@
+BIN_MV        = mv
+BIN_SED       = sed
+BIN_INSTALL   = @INSTALL@
+BIN_MSGMERGE  = @MSGMERGE@
+BIN_XGETTEXT  = @XGETTEXT@
+BIN_MSGFMT    = @MSGFMT@
+BIN_CC        = @CC@
+BIN_CXX       = @CXX@
+BIN_CP        = cp
+BIN_MKDIR     = mkdir
+BIN_MKDIR_P   = mkdir -p
+BIN_ECHO      = echo
+BIN_TOUCH     = touch
+BIN_LUA       = @LUA@
 BIN_WX_CONFIG = @WX_CONFIG_PATH@
+
+################
+# APPIMAGE TOOLS
+################
+TOOL_LINUXDEPLOY = $(TOM)tools/linuxdeploy
+TOOL_APPIMAGE    = $(TOP)tools/appimagetool.AppImage
diff --git a/Makefile.target b/Makefile.target
index fd40d87bfe92fcac59f3b8719433ab3308e6ee25..d0ad20cbeab342b269175296a093d20cfdeec43e 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -1,5 +1,8 @@
 ifneq (yes, $(INCLUDING_CHILD_MAKEFILES))
 COMMANDS := all install clean distclean test depclean osx-bundle osx-dmg test-automation test-libaegisub style tags
+ifeq (yes, $(BUILD_LINUX))
+COMMANDS := $(COMMANDS) appimage
+endif
 .PHONY: $(COMMANDS)
 .DEFAULT_GOAL := all
 
@@ -41,10 +44,9 @@ endef
 $(foreach target,$(LIB),$(eval $(call set_target_flags,$(target),$(TOP)lib/lib$(target).a)))
 $(foreach target,$(PROGRAM),$(eval $(call set_target_flags,$(notdir $(target)),$(target))))
 
-# Create the build and install targets for programs
-# Not done with a pattern rule since the pattern would be just %: and doing that
-# leads to make trying to use gcc to make any nonexistent targets rather than
-# erroring
+# Create the build and install targets for programs.  Not done with a pattern
+# rule since the pattern would be just %: and doing that leads to make trying
+# to use gcc to make any nonexistent targets rather than erroring
 define create_program_targets
   ifdef $1_INSTALLNAME
 install: $(DESTDIR)$(P_BINDIR)/$($1_INSTALLNAME)
@@ -80,6 +82,7 @@ all: $(LIB_TARGETS)
 
 clean:
 	$(BIN_RM) -f $(OBJ) $(CLEANFILES) $(LIB_TARGETS)
+	$(BIN_RM) -rf $(CLEANDIRS)
 
 distclean: clean
 	$(BIN_RM) -rf $(DISTCLEANFILES) $(DEP)
@@ -90,16 +93,20 @@ depclean: clean
 install:
 
 style:
-	./tools/astyle.bash
+	$(TOP)tools/astyle.bash
 
 tags:
-	./tools/tags.bash
+	$(TOP)tools/tags.bash
+
+appimage: src/aegisub packages/desktop/Aegisub.desktop packages/desktop/aegisub.png install
+	$(TOOL_LINUXDEPLOY) --appdir Aegisub --desktop-file packages/desktop/Aegisub.desktop --icon-file packages/desktop/aegisub.png --executable src/aegisub
+	$(TOOL_APPIMAGE) Aegisub
 
 # The actual build rules
 .SUFFIXES:
 
 CXX_CMD = $(CXX_ENV) $(BIN_CXX) $(CFLAGS_DEP) $(CPPFLAGS)
-CC_CMD  = $(CXX_ENV) $(BIN_CC) $(CFLAGS_DEP) $(CPPFLAGS)
+CC_CMD  = $(CXX_ENV) $(BIN_CC)  $(CFLAGS_DEP) $(CPPFLAGS)
 POST_FLAGS = $($@_FLAGS) -c -o $@ $<
 
 %.o: %.c	; $(CC_CMD) $(CFLAGS) $(POST_FLAGS)
@@ -115,9 +122,9 @@ POST_FLAGS = $($@_FLAGS) -c -o $@ $<
 
 .SECONDEXPANSION:
 
-# Libraries contain all object files they depend on (but they may depend on other files)
-# Not using libtool on OS X because it has an unsilenceable warning about a
-# compatibility issue with BSD 4.3 (wtf)
+# Libraries contain all object files they depend on (but they may depend on
+# other files). Not using libtool on OS X because it has an unsilenceable
+# warning about a compatibility issue with BSD 4.3 (wtf)
 lib%.a: $$($$*_OBJ)
 	@$(BIN_MKDIR_P) $(dir $@)
 	$(BIN_AR) cru $@ $(filter %.o,$^)
diff --git a/README.md b/README.md
index 02d7240232ab9e5cc8ee7c798718b7048b1d850f..8632f33e60b197764fd29685e8b5c9d391b1e8b8 100644
--- a/README.md
+++ b/README.md
@@ -56,12 +56,15 @@ On debian buster, the packages are the following:
 - libboost-thread-dev
 - astyle
 
+### Build the binary
+
 Once all the dependencies are installed, run:
 
 ```bash
 autoreconf
-./configure --enable-debug --with-libpulse
+./configure --enable-debug --enable-debug-exceptions --with-libpulse --disable-update-checker --enable-silent-rules --without-system-luajit
 make -j$(nproc)
+make install
 ```
 
 You may also consider the following rules:
@@ -71,6 +74,24 @@ You may also consider the following rules:
 - `make tags` to build the ctags and cscope databases
 - `make style` to format your code with the right style
 
+### Build an appimage
+
+First, install `patchelf` (`apt install patchelf` or similar for your distro)
+and [linuxdeploy](https://github.com/linuxdeploy/linuxdeploy).  An AppImage of
+linuxdeploy can be found [here](https://martinm.iiens.net/linuxdeploy).  You
+will also need to download the appimagetool binary and save it to the
+[tools](tools) folder. If you don't do it manually, it will be done for you at
+some point by the build scripts.
+
+To build the AppImage, juste use the following commands:
+
+```bash
+autoreconf
+./configure --with-libpulse --enable-silent-rules --disable-update-checker --without-system-luajit --enable-appimage
+make -j$(nproc)
+make appimage
+```
+
 ### Windows
 
 Prerequisites:
diff --git a/config.guess b/config/config.guess
similarity index 100%
rename from config.guess
rename to config/config.guess
diff --git a/config.sub b/config/config.sub
similarity index 100%
rename from config.sub
rename to config/config.sub
diff --git a/install-sh b/config/install-sh
similarity index 100%
rename from install-sh
rename to config/install-sh
diff --git a/configure.ac b/configure.ac
index 2c1e2d9f51ee8d04d1ec29a270715ae872fdda28..f27177baf7f1d37f1534df9e5a35846f0b975eb3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,12 +7,21 @@ AC_INIT([Aegisub], [aegisub_version],, [aegisub])
 : ${CFLAGS=""}
 : ${CXXFLAGS=""}
 AC_CONFIG_SRCDIR([src/main.cpp])
+AC_CONFIG_AUX_DIR([config])
 AC_CONFIG_HEADER([acconf.h])
 AC_CONFIG_MACRO_DIR([m4macros])
 AC_GNU_SOURCE
 AC_CANONICAL_HOST
 AM_SILENT_RULES([yes])
 
+##########################
+# Only run from source dir
+##########################
+AEGISUB_PATH_SOURCE="$(dirname $(readlink -f "$0"))"
+AEGISUB_PATH_BUILD="$(pwd)"
+AS_IF([! test "$AEGISUB_PATH_BUILD" = "$AEGISUB_PATH_BUILD"],
+      [AC_MSG_ERROR([The build directory must be the same as the source directory])])
+
 ###################
 # Required packages
 ###################
@@ -41,6 +50,7 @@ AS_CASE([$host],
         [*-*-linux*],  [build_linux="yes"])
 
 AC_SUBST(build_darwin)
+AC_SUBST(build_linux)
 
 ########################
 # Configurable variables
@@ -75,6 +85,48 @@ AC_SUBST(P_ICON)
 # set it to $ac_default_prefix if it hasn't been supplied.
 AS_CASE([x$prefix], [xNONE | x], [prefix="$ac_default_prefix"], [])
 
+# Build with appimage support
+# Do this here because we will override the install path
+AC_ARG_ENABLE([appimage],
+              [AS_HELP_STRING([--enable-appimage], [Build Aegisub as an AppImage. Defaults to no])],
+              [AEGISUB_APPIMAGE_ENABLED=$enableval],
+              [AEGISUB_APPIMAGE_ENABLED=no])
+AS_IF([test "x$AEGISUB_APPIMAGE_ENABLED" = "xyes"], [
+    # Check more binaries
+    AC_PATH_PROG([PATCHELF], [patchelf])
+    AC_PATH_PROG([WGET], [wget])
+    AC_PATH_PROG([CHMOD], [chmod])
+
+    AS_IF([test "$PATCHELF" = "notfound"],  [AC_MSG_ERROR([patchelf is required for AppImage generation])])
+    AS_IF([test "$WGET" = "notfound"],      [AC_MSG_ERROR([wget is required for AppImage generation])])
+    AS_IF([test "$CHMOD" = "notfound"],     [AC_MSG_ERROR([chmod is required for AppImage generation])])
+
+    # Download the AppImage creation tool, transforms the AppDir into an AppImage
+    AC_MSG_CHECKING([Downloading AppImage creation tool])
+    $WGET https://github.com/probonopd/AppImageKit/releases/download/continuous/appimagetool-`arch`.AppImage -O tools/appimagetool.AppImage -o config.wget.log
+    $CHMOD 00700 tools/appimagetool.AppImage
+    AS_IF([test $? -eq 0 ], [
+        AC_MSG_RESULT([done])
+    ], [
+        AC_MSG_FAILURE([failed])
+    ])
+
+    # Downlaod the AppDir management tool, create the AppDir
+    AC_MSG_CHECKING([Downloading LinuxDeploy creation tool])
+    $WGET https://martinm.iiens.net/linuxdeploy -O tools/linuxdeploy -a config.wget.log
+    $CHMOD 00700 tools/linuxdeploy
+    AS_IF([test $? -eq 0 ], [
+        AC_MSG_RESULT([done])
+    ], [
+        AC_MSG_FAILURE([failed])
+    ])
+
+    # Override prefix
+    prefix="$PWD/Aegisub/usr"
+], [
+    AC_MSG_NOTICE([Don't build with AppImage support])
+])
+
 # Install prefix used by wxStandardPaths::SetInstallPrefix.
 AC_DEFINE_UNQUOTED([INSTALL_PREFIX], ["$prefix"], [Default install prefix, or --prefix.])
 
diff --git a/header.mk b/header.mk
index a9ce1d77872210a4a51f4d7794ab138ffe33756c..e9c7721b2ff196f4e2d34f1fff264cb594a1e60c 100644
--- a/header.mk
+++ b/header.mk
@@ -33,11 +33,22 @@ DISTCLEANFILES += \
 	$(TOP)build/git_version.h \
 	$(TOP)Makefile.inc \
 	$(TOP)config.log \
+	$(TOP)config.wget.log \
 	$(TOP)acconf.h.in \
 	$(TOP)config.status \
 	$(TOP)autom4te.cache \
 	$(TOP)aclocal.m4 \
 
+CLEANFILES += \
+	$(wildcard $(TOP)Aegisub/usr/lib/*) \
+	$(TOP)Aegisub-x86_64.AppImage \
+	$(TOP)cscope.files \
+	$(TOP)cscope.out \
+	$(TOP)tags \
+
+CLEANDIRS += \
+	$(TOP)Aegisub \
+
 define MKDIR_INSTALL
 @$(BIN_MKDIR_P) $(dir $@)
 $(BIN_INSTALL) -m644 $< $@
diff --git a/libaegisub/lua/modules/lfs.cpp b/libaegisub/lua/modules/lfs.cpp
index 38f29d7928852a64ba247379c0ff366c067b6220..8053b6893a76fc28af0a6a77d308c5a86af398c8 100644
--- a/libaegisub/lua/modules/lfs.cpp
+++ b/libaegisub/lua/modules/lfs.cpp
@@ -112,7 +112,7 @@ DirectoryIterator *dir_new(const char *path, char **err)
 
 const char *get_mode(const char *path, char **err)
 {
-    return wrap(err, [ = ]() -> const char * {
+    return wrap(err, [ = ]() -> const char* {
         switch (bfs::status(path).type())
         {
         case bfs::file_not_found: return nullptr;         break;
diff --git a/packages/desktop/Aegisub.desktop b/packages/desktop/Aegisub.desktop
new file mode 100644
index 0000000000000000000000000000000000000000..e39d8570e59ae2516a7a70b16ba09cd95751908e
--- /dev/null
+++ b/packages/desktop/Aegisub.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Aegisub
+Icon=aegisub
+GenericName=Subtitle Editor
+Comment=Create and edit subtitles for film and videos.
+Keywords=subtitles;video;audio;text
+Terminal=false
+Categories=AudioVideo;AudioVideoEditing;
+MimeType=application/x-srt;text/plain;text/x-ass;text/x-microdvd;text/x-ssa;
+StartupNotify=true
+Exec=aegisub
diff --git a/packages/desktop/aegisub.png b/packages/desktop/aegisub.png
new file mode 120000
index 0000000000000000000000000000000000000000..078e90af053092ec5cfbd41bcabc28fbaa7eb43e
--- /dev/null
+++ b/packages/desktop/aegisub.png
@@ -0,0 +1 @@
+64x64.png
\ No newline at end of file
diff --git a/src/mkv_wrap.cpp b/src/mkv_wrap.cpp
index acb4be0e7c9732b0be224ea1f3ad2bdc0c26c282..bc9aa58d97a978112a6e5279b3822a6574cb01a3 100644
--- a/src/mkv_wrap.cpp
+++ b/src/mkv_wrap.cpp
@@ -105,7 +105,7 @@ struct MkvStdIO final : InputStream {
         read = &MkvStdIO::Read;
         scan = &MkvStdIO::Scan;
         getcachesize = [](InputStream *) -> unsigned int { return 16 * 1024 * 1024; };
-        geterror = [](InputStream * st) -> const char * { return ((MkvStdIO *)st)->error.c_str(); };
+        geterror = [](InputStream * st) -> const char* { return ((MkvStdIO *)st)->error.c_str(); };
         memalloc = [](InputStream *, size_t size) { return malloc(size); };
         memrealloc = [](InputStream *, void *mem, size_t size) { return realloc(mem, size); };
         memfree = [](InputStream *, void *mem) { free(mem); };
diff --git a/tools/copy-libs.lua b/tools/copy-libs.lua
new file mode 100755
index 0000000000000000000000000000000000000000..eb674ab20a795f66a19b7279d08a502e8b72311b
--- /dev/null
+++ b/tools/copy-libs.lua
@@ -0,0 +1,76 @@
+#!/usr/bin/env lua
+-- Needs awk and a shell (usually bash / sh) and ldd, which is a Linux utility.
+-- Thus run only this script on a Linux env or where the .so files can be found
+-- with the ldd command.
+
+if not arg or #arg ~= 2 then os.exit(1) end
+
+io.stdout:write(string.format('Running for file %s\n', arg[1]))
+io.stdout:write(string.format('Dest folder is %s\n',   arg[2]))
+
+-- Libs tables
+LIBS_WRITE = {}
+LIBS_READ  = {}
+
+-- Capture the output of a command
+function os.capture(cmd)
+    local f = assert(io.popen(cmd, 'r'))
+    local s = assert(f:read('*a'))
+    f:close()
+    return s
+end
+
+-- Simple copy of a table, it doesn't work with metatables and recursive tables
+function copy_simple(obj)
+    if type(obj) ~= 'table' then return obj end
+    local res = {}
+    for k, v in pairs(obj) do res[copy_simple(k)] = copy_simple(v) end
+    return res
+end
+
+-- Add the content of t2 in t1
+function concatenate(t1, t2)
+    for name, file in pairs(t2) do
+        t1[name] = file
+    end
+end
+
+-- Get the number of elements in a table
+function tablelength(T)
+    local count = 0
+    for _ in pairs(T) do count = count + 1 end
+    return count
+end
+
+-- The ldd command line utility, but in lua
+function ldd(file)
+    local libs = {}
+    local cmd  = string.format('ldd %s | awk \'$2 = "=>" && $3 { print $1 " " $3 }\'', arg[1])
+    local out  = os.capture(cmd)
+
+    for name, file in out:gmatch("([^\n ]*) ([^\n ]*)[\n]") do
+        libs[name] = file
+    end
+
+    return libs
+end
+
+-- Add NEEDS for each lib while there are some added. It's like a dataflow algorithme.
+LIBS_WRITE = ldd(arg[1])
+while tablelength(LIBS_READ) ~= tablelength(LIBS_WRITE) do
+    LIBS_READ = copy_simple(LIBS_WRITE)
+
+    for name, file in pairs(LIBS_READ) do
+        concatenate(LIBS_WRITE, ldd(file))
+    end
+end
+
+io.stdout:write(string.format("Cleaning %s", arg[2]))
+os.execute(string.format("rm -f %s/*", arg[2]))
+
+for name, file in pairs(LIBS_WRITE) do
+    local cmd = string.format('cp %s %s/%s && chmod 555 %s/%s',
+                              file, arg[2], name, arg[2], name)
+    os.execute(cmd)
+end
+io.stdout:write(string.format("Copied %d libraries to %s\n", tablelength(LIBS_WRITE), arg[2]))