Skip to content

Instantly share code, notes, and snippets.

@meramsey
Forked from mnesarco/build-installer.py
Created July 24, 2021 14:37
Show Gist options
  • Save meramsey/b2a01dcbe37a83e03eabe540266446b3 to your computer and use it in GitHub Desktop.
Save meramsey/b2a01dcbe37a83e03eabe540266446b3 to your computer and use it in GitHub Desktop.
AppImage Auto Installer Builder
#!/usr/bin/python3
#
# Copyright 2020 Frank David Martinez M. (mnesarco at github)
#
# 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
#
# 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.
#
# +------------------------------------------------------------------------+
# | AppImage Auto Installer Builder |
# +------------------------------------------------------------------------+
# | |
# | Converts an AppImage into a POSIX shell script that will |
# | deploy the AppImage to $HOME/.appimage and create valid icon |
# | and desktop launcher in $HOME/.local/shared/... |
# | |
# | usage: build-installer.py [-h] --appimage IMAGE [--exe-name EXE] |
# | [--app-name NAME] [--app-category CATEGORY] |
# | [--icon ICON] [--appimage-version VERSION] |
# | |
# | AppImage Installer Builder |
# | |
# | arguments: |
# | -h, --help show this help message and exit |
# | --appimage IMAGE, -a IMAGE |
# | AppImage file |
# | --exe-name EXE, -e EXE |
# | Final name of the installed executable |
# | --app-name NAME, -n NAME |
# | Final name of the application |
# | --app-category CATEGORY, -c CATEGORY |
# | Application's category |
# | --icon ICON, -i ICON Icon file path |
# | --appimage-version VERSION, --iv VERSION |
# | AppImage version |
# | |
# | Result: |
# | The result is a shell script named ${EXE}-installer.sh ready to be |
# | distributed. The final user downloads the installer and execute it. |
# | after that, the AppImage is installed and have a launcher. |
# +------------------------------------------------------------------------+
import sys, argparse, os, re, stat
# -------------------------
# CLI Arguments
# -------------------------
parser = argparse.ArgumentParser(description='AppImage Installer Builder')
parser.add_argument('--appimage', '-a', help='AppImage file', required=True, dest="image")
parser.add_argument('--exe-name', '-e', help='Final name of the installed executable', dest="exe")
parser.add_argument('--app-name', '-n', help='Final name of the application', dest="name")
parser.add_argument('--app-category', '-c', help='Application\'s category', dest="category")
parser.add_argument('--icon', '-i', help='Icon file path', dest="icon")
parser.add_argument('--appimage-version', '--iv', help='AppImage version', dest="version")
args = parser.parse_args()
if not os.path.exists(args.image):
print("File {0} does not exists".format(args.image))
exit(1)
if not args.exe:
args.exe = re.compile(r'^[^\-]+').search(args.image).group()
if not args.name:
args.name = args.exe
if not args.category:
args.category = "Utility"
if not args.version:
ver = re.compile(r'^[^\-]+-([^\-]+)').search(args.image)
if ver:
args.version = ver.group(1)
else:
args.version = '1'
if not args.icon:
args.icon = 'icon.svg'
if not os.path.exists(args.icon):
args.icon = 'icon.png'
if not os.path.exists(args.icon):
print("File {0} does not exists".format(args.icon))
exit(1)
if args.icon.lower().endswith('.svg'):
icon_type = 'scalable'
else:
icon_type = '128x128'
icon_name = args.exe.lower()
# -------------------------
# XDG Desktop file
# -------------------------
desktop="""
[Desktop Entry]
Name={name}
Exec=$HOME/.appimage/{exe} %f
Icon={icon}
Type=Application
Categories={cat};
X-AppImage-Integrate=true
Name[en_US]={exe}.desktop
X-AppImage-Version={ver}
TryExec=$HOME/.appimage/{exe}
Terminal=false
Comment=
""".format(name=args.name, exe=args.exe, icon=icon_name, cat=args.category, ver=args.version)
# -------------------------
# File offsets
# -------------------------
icon_size = os.stat(args.icon).st_size
image_size = os.stat(args.image).st_size
icon_start = 1024
image_start = icon_start + icon_size
# -------------------------
# File paths
# -------------------------
icon_dir="$HOME/.local/share/icons/hicolor/{0}/apps".format(icon_type)
icon_file="{0}/{1}.{2}".format(icon_dir, icon_name, 'svg' if icon_type == 'scalable' else 'png')
desktop_dir="$HOME/.local/share/applications"
desktop_file="{0}/{1}.desktop".format(desktop_dir, args.exe)
image_dir="$HOME/.appimage"
image_file="{0}/{1}".format(image_dir, args.exe)
# -------------------------
# Shell script
# -------------------------
script = """#!/bin/bash
cat > _tmp_desktop <<EOM{desktop}
EOM
mkdir -p "{icon_dir}"
dd if="$0" of={icon_file} bs=1 skip={icon_start} count={icon_size}
mkdir -p "{desktop_dir}"
mv _tmp_desktop {desktop_file}
chmod +x {desktop_file}
gio set {desktop_file} "metadata::trusted" yes
mkdir -p "{image_dir}"
tail -c{image_size} "$0" > {image_file}
chmod +x {image_file}
ln -sf {desktop_file}
exit 0
""".format(
desktop=desktop,
icon_dir=icon_dir,
icon_file=icon_file,
icon_start=icon_start,
icon_size=icon_size,
desktop_dir=desktop_dir,
desktop_file=desktop_file,
image_dir=image_dir,
image_file=image_file,
image_start=image_start,
image_size=image_size,
exe=args.exe)
# -------------------------
# Pad alignment
# -------------------------
pad = icon_start - len(script.encode('ascii'))
script += "#" * pad
# -------------------------
# Write Installer
# -------------------------
installer = "{exe}-installer.sh".format(exe=args.exe)
with open(installer, 'wb') as f:
f.write(script.encode('ascii'))
with open(args.icon, 'rb') as icon_f:
f.write(icon_f.read())
with open(args.image, 'rb') as image_f:
while True:
dat = image_f.read(8192)
if not dat:
break
f.write(dat)
st = os.stat(installer)
os.chmod(installer, st.st_mode | stat.S_IEXEC)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment