linux_installer

package module
v0.0.0-...-ca33ec9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2023 License: CC0-1.0 Imports: 27 Imported by: 0

README

Linux Installer

Go Reference License

Graphical Linux application installer for audiences that are used to Windows installers. When all you would really need is unzip, but want a nice user experience nonetheless. Imitates the look and feel of NSIS, or rather Wizard97-type installers.

Screenshot

(Example installer splash image by Deniz Altindas and banner image by Victoria via unsplash.com)

Features

  • Application menu entry creation (.desktop-files)
  • Pre-/post-install script hooks
  • Automatic uninstaller script creation
  • Commandline or "silent" mode
  • Cancel with full rollback during install process
  • Run application after finish
  • Full internationalization for both GUI and CLI
Missing Features
  • No partial installation, no installation by "components"
  • No automatic detection of previous installations for updates

Contents

Quickstart: Run the Example-Installer

Download the latest builder and extract the setup-installer-builder executable from it.

# Run the Setup for the installer-builder (it's a showcase within a showcase 🙂)
chmod +x setup-installer-builder
./setup-installer-builder

# Go to the installed path
cd ~/LinuxInstallerBuilder/  # (or the path you selected during installation)

# (Add/edit file(s) in the "data" subfolder here, but the example has one already.)

# Create the example installer, and give it a version of 1.0 (optional)
make VERSION=1.0

# Run the default output file
./Setup

Dev Setup & Build a Linux Installer

Overview

This project creates an installer builder, which is used to create the actual installers. The installer builder comes in the shape of a zip archive containing:

  • an empty data/ source folder for your files
  • the naked linux-installer binary
    • "naked" means "only the installer logic", your payload(s) will be appended to this file later to create the full installer.
  • all installer GUI files in resources/ — to customize and modify
  • a Makefile/make.bat (for building on Linux/Windows respectively) to put it all together

The installer builder has no external dependencies (except for make on Linux, and Powershell on Windows), and can be run on a minimal dedicated build machine.

Requirements
  • The following dependencies need to be installed to build:

    go gcc gtk3 make pkg-config zip

    (Technically, BSD-Make works just as well as GNU-Make, so if you have bmake that'll work too).

    Prepared install commands for popular distributions are below:

    Archlinux / Manjaro

    sudo pacman -S --needed go gcc gtk3 make pkg-config zip

    Debian / Ubuntu

    sudo apt install golang-go gcc libgtk-3-dev libglib2.0-dev make pkg-config zip

    Gentoo

    sudo emerge dev-lang/go x11-libs/gtk+:3 dev-libs/glib dev-util/pkgconfig app-arch/zip

    Fedora / RedHat

    sudo dnf install golang gcc gtk3-devel make pkgconf zip

  • If you're new to Go, verify that your installation works by running go version. It should print the installed Go version.

  • If you want to edit the installer GUI layout you should install Glade as well.

Setup

These steps need to be executed only initially and after updating the installer builder itself.

  1. Install all dependencies.
  2. cd into the local copy of this repository and run make linux-builder.zip.
  3. Copy the resulting linux-builder.zip archive to builder machine (if different). The builder machine can run Linux or Windows.
  4. Extract anywhere.
Build

These steps need to be executed in the extracted builder directory, and for every installer.

  1. Add necessary application files to the data subfolder
  2. Set variables.version (and possibly other variables) in resources/config.yml
  3. Run make to create installer.
    cd ~/path/to/linux-builder
    make
    # or set version and filename directly on the commandline:
    make VERSION=10.0 OUTPUT=Setup_ExampleApp_v1.1
    
  4. The same on Windows:
    cd /d D:\Data\linux-builder\
    make.bat
    :: or:
    make.bat VERSION=10.0 OUTPUT=Setup_ExampleApp_v1.1
    
    If you set the variables in resources/config.yml before you can also simply run make.bat by double-clicking.
Speed-Up Installer Creation

You can pre-compress files that are the same for several installers into zip archives and put them into the data_compressed folder. This can speed up the creation of a batch of mostly-similar installers.

Note: The archive name data.zip is used by the builder, so don't use that specific filename.

Customization

Various parts of the installer can be customized and changed without touching the code, such as the style and layout of elements, and the translation strings. The way to do this is described in this section.

Adding a new screen (or removing one) requires only minor code changes, and is explained below as well.

Images

The splash.bmp image is shown on the left-hand side of the language-, welcome- and success-/failure screens, and should be a vertical 164×314 pixels in size. The right side of the image should connect well with the window.background color (currently plain white #ffffff).

The banner.bmp image is shown on the top of all other screens and should be a horizontal 497×60 pixels in size. The bottom of the image should connect well with the window.background color like above.

The icon.gif image is used as the icon in the taskbar and possibly the window's title bar while the installer is running. It should be a square GIF image, not too large (16×16 or 32×32 pixels).

Installer Style & Layout

The GUI layout of the installer is specified in resources/gui/gui.glade. This file can be edited with Glade, a WYSIWYG GTK3 UI editor.

The layout consists of the main installer window and a do-you-really-want-to-quit dialog box.

The installer window consists mainly of the "Stack" of screens that the installer can go through. Not all have to be visited (e.g. installation failure), and not necessarily in order (although they are mostly run through sequentially).

GUI CSS

GTK3 supports styling UI elements with CSS. Elements can have ids, classes and are of a type, just like regular HTML elements.

The GUI CSS is loaded from a variable in resources/config.yml called gui_css, which controls some colors in the GUI, and mostly sets the background color to white, and changes some font properties so the license text is not too large, and the filenames in the installer progress not too prominent.

To change the styling of the installer, simply change the content of that variable. See the GTK3 CSS docs for information and both the example config.yml and the config.yml for this repository's installer for hints.

Hooks

Before installation starts, and after, there is the possibility of running a custom script to do any tasks necessary at that time. Currently these are present, but empty.

The hook scripts live in resources/hooks/ and are named after their execution time, namely pre-install.sh and post-install.sh, which run before and after installation, respectively.

You can write custom commands into these files and they will be executed. Their output (for debugging purposes) is logged into the installer.log file that is created when the installer is run.

New Language Translation

In short: Add a new file named xx.yml inside resources/languages/ (or better, copy en.yml), where "xx" is the language's two-letter ISO 639-1 code. Then translate all messages. The language should now be available in the language selection in the GUI and the help text in the CLI mode.

E.g. in order to add French, create and translate resources/languages/fr.yml.

New Installer Screens

A new installer screen is a two-step process:

  1. Layout: Create a new installer screen in resources/gui/gui.glade
  2. Behavior: Add screen to screenHandlers() inside gui/gui.go
Layout

In the list of screens in Glade, right-click on the screen after which you want to insert your new screen, and select "Insert Page After" at the bottom of the context menu. Double-click inside the resulting empty space and select GtkBox to create a layout base for the new screen. Give the Box an ID in the details panel on the right in the "General" tab. This ID is needed in the next step.

You can then design the inside of the Box however you like. Remember to add IDs to relevant elements in order to reach them from the behavior code.

Behavior

In gui/gui.go inside screenHandlers() add a new section for the screen like this:

    {
        name: "myscreen",
        disabled: false,
        before: func() {
            // ...
        },
        after: func() {
            // ...
        },
        undo: func() bool {
            // ...
        }
    },

The "name" key is the ID you chose in the layout step. The "disabled", "before", "after" and "undo" keys are documented in the comment for the ScreenHandler struct a little bit earlier in gui/gui.go. See there for details.

If a function for a key is empty (or if "disabled" is false), the key can be omitted completely.

Hacking

See HACKING.md.

Troubleshooting

libc.so.6: version GLIBC_... not found

The installer might fail to run on another machine with an error like this:

./linux-installer-dev: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./linux-installer-dev)

The installer builder was linked on a machine with a newer libc than the one on the running system. The solution is to build on a system with the lowest libc version that you want to target. Generally this means to build the installer builder on the oldest Linux version that you are targeting.

To check which version you are running, run ldd --version.

(Note that this does not affect the machine on which you are packaging the actual installers. You can run the installer builder on any system.)

License and Acknowledgments

This software has a CC0 license.

To the extent possible under law, all copyright and related or neighboring rights to this work are waived.

(And yes, this means commercial use is explicitly allowed.)

This software was developed and tested for years at Contecs engineering services GmbH who have graciously allowed publication as free and open source software. Thank you very much!

Documentation

Overview

Graphical Linux application installer for audiences that are used to Windows installers. Imitates the look and feel of NSIS, or rather Wizard97-type installers.

This installer will bundle any files and folders inside the "data" directory, to be installed via a GTK3-based GUI, or alternatively a commandline interface (referred to as a "silent" install).

See the README.md for usage info and customization instructions and see HACKING.md for a code structure overview.

Index

Constants

View Source
const (
	B   int64 = 1
	KiB       = 1024 * B
	MiB       = 1024 * KiB
	GiB       = 1024 * MiB
	TiB       = 1024 * GiB
	PiB       = 1024 * TiB
)
View Source
const (
	DefaultLanguage string = "en"
)

Variables

This section is empty.

Functions

func ExpandAllVariables

func ExpandAllVariables(
	str string,
	variables VariableMap,
	untypedVariables map[string]interface{},
) (expanded string)

ExpandAllVariables is the same as ExpandVariables, except that it additionally takes untypedVariables, a string map of values of arbitrary type.

func ExpandVariables

func ExpandVariables(str string, variables VariableMap) (expanded string)

ExpandVariables takes a string with template variables like {{.var}} and expands them with the given variables map.

func GetResource

func GetResource(name string) (string, error)

GetResource returns the contents of of a resources file with the given name as a string. If the file does not exists it returns an error.

func GetResourceFiltered

func GetResourceFiltered(name string, dirFilter *regexp.Regexp) (map[string]string, error)

GetResourceFiltered returns the contents of multiple resource files within the subdir specified by name, and the filename regexp given by dirFilter. If the directory name does not exist it returns an error.

func MustGetResource

func MustGetResource(name string) string

MustGetResource returns the contents of a resources file with the given name as a string. If the file does not exists it panics.

func MustGetResourceFiltered

func MustGetResourceFiltered(name string, dirFilter *regexp.Regexp) map[string]string

MustGetResourceFiltered returns the contents of multiple resource files within the subdir specified by name, and the filename regexp given by dirFilter. If the directory name does not exist it panics.

func Run

func Run() int

Run parses commandline options (if any) and starts one of two installer modes, GUI or commandline mode.

Commandline parameters are:

-target   // Target directory to install to
-license  // Print the software license and exit
-accept   // Accept the license. (This flag is only available if
          // "must_accept_license_on_cli" is set in the config file.)
-lang     // Choose install language. This also affects the GUI mode.
-run      // Run installed application after successful install.

Giving any commandline parameters other than -lang will trigger commandline, or "silent" mode. -target (and -accept if configured) are necessary to run commandline install. -lang will also set the default GUI language. It cannot, however, set the language of the -help output.

func RunCliInstall

func RunCliInstall(
	installerTempPath, target string, translator *Translator, config *Config,
)

RunCliInstall runs a "silent" installation, in the terminal with no further user interaction.

func RunGuiInstall

func RunGuiInstall(
	installerTempPath string, translator *Translator, config *Config,
) (err error)

RunGuiInstall loads the gui.so plugin, and starts the installer GUI.

If the GUI can't be loaded for some reason, an error is returned. Most common reasons for error include (on Linux):

  • no desktop running (headless servers, remote logins)
  • GTK3 missing (Redhat/Centos 6 or older)

When the GUI fails to load it will try a last-ditch effort to show an error dialog with Zenity (which comes with e.g. RedHat/Centos 6). Beyond that there is no way to interact with the user graphically, and it will simply log the error, and print usage help to the terminal. (Which of course, will only be visible if started via commandline and not via double-click.)

func RunTuiInstall

func RunTuiInstall(installerTempPath string, translator *Translator) (err error)

RunTuiInstall would start a terminal curses-based UI (unfinished and disabled).

func UnpackDataDir

func UnpackDataDir(from string, to string) error

UnpackDataDir copies all data files from a subdir given by from to a path given by to. It returns an error if the boxes aren't opened yet, the path can't be written to, or anything else goes wrong.

func UnpackResourceDir

func UnpackResourceDir(from string, to string) error

UnpackResourceDir copies all resource files from a subdir given by from to a path given by to. It returns an error if the boxes aren't opened yet, the path can't be written to, or anything else goes wrong.

Types

type BoxFile

type BoxFile struct {
	// contains filtered or unexported fields
}

type Config

type Config struct {
	Variables             VariableMap `yaml:"variables,omitempty"`
	MustAcceptLicense     bool        `yaml:"must_accept_license"`
	DefaultInstallDirName string      `yaml:"default_install_dir_name"`
	GuiCss                string      `yaml:"gui_css,omitempty"`

	// commandline config options
	NoLauncher   bool
	RunInstalled bool
}

Config holds a list of variables to be expanded in message strings, as well other settings for the installer.

Setting MustAcceptLicenseOnCli to true (the default) enables & requires the -accept flag.

DefaultInstallDirName is a string or template for the default application directory, into which to install.

NoLauncher is a flag from the command line that suppresses launcher shortcut creation.

RunInstalled is a flag from the command line that runs the installed application after installation completes successfully.

func NewConfig

func NewConfig() (*Config, error)

NewConfig returns a Config object containing the settings from resources/config.yml.

type InstallFile

type InstallFile struct {
	*zip.File
	Target string
	// contains filtered or unexported fields
}

InstallFile is an augmented zip.FileInfo struct with both source and target path as well as a flag indicating wether the file has been copied to the target or not. Source and target path will be the same if the installation doesn't run from a subdir of the source data.

type InstallStatus

type InstallStatus struct {
	S       string
	File    *InstallFile
	Done    bool
	Aborted bool
}

InstallStatus is a message struct that gets passed around at various times in the installation process. All fields are optional and contain the current file, a status string, wether the installer as a whole is finished or not, or wether it's been aborted and rolled back.

type Installer

type Installer struct {
	Target         string
	Status         *InstallStatus
	CreateLauncher bool
	Done           bool
	// contains filtered or unexported fields
}

Installer represents a set of files and a target to be copied into. It contains information about the files, size, and status (done or not), as well as 3 different message channels, for each abort and its confirmation as well as status channel.

func NewInstaller

func NewInstaller(tempPath string, config *Config) *Installer

NewInstaller creates a new Installer. You will still need to set the target path after initialization:

installer := NewInstaller()
/* ... some other stuff happens ... */
installer.Target = "/some/output/path"
/* and go: */
installer.StartInstall()

Alternatively you can just use NewInstallerTo() and set the target directly:

installer := NewInstallerTo("/some/output/path/")
installer.StartInstall()
/* some watch loop with 'installer.Status()' */

func NewInstallerTo

func NewInstallerTo(target string, tempPath string, config *Config) *Installer

NewInstallerTo creates a new installer with a target path.

func (*Installer) Abort

func (i *Installer) Abort()

Abort can be called to stop the installer. The installer will usually not stop immediately, but finish copying the current file.

Use Rollback() instead of Abort() if you also want all files and directories rolled back and deleted.

func (*Installer) CheckSetInstallDir

func (i *Installer) CheckSetInstallDir(installPath string) error

CheckSetInstallDir checks if the given directory is a valid, writable path. If it is it sets it as the installer's target directory. Returns err when the installPath exists but is not a directory, or when installPath (or the nearest existing parent) is not writable.

func (*Installer) DiskSpaceSufficient

func (i *Installer) DiskSpaceSufficient() bool

DiskSpaceSufficient returns true when the total size of files to be installed is smaller than the remaining available space on the disk that contains the installer's target path.

func (*Installer) Error

func (i *Installer) Error() error

Error returns the latest insatller error or nil.

func (*Installer) ExecInstalled

func (i *Installer) ExecInstalled()

ExecInstalled changes into the installer target directory, runs the start command set in the config file. This function replaces (or, on Windows, terminates) the installer process and never returns.

func (*Installer) NextFile

func (i *Installer) NextFile() *InstallFile

NextFile returns the file that the installer will install next, or the one that is currently being installed.

func (*Installer) PostInstall

func (i *Installer) PostInstall(variablesList ...VariableMap)

PostInstall runs a post-install script & creates an uninstaller as well as an optional launcher entry for the program.

func (*Installer) PreInstall

func (i *Installer) PreInstall()

PreInstall runs a pre-install script, if a file hooks/pre-install.* exists in the resource directory. The file extension is OS-specific (.sh for Linux, .bat for Windows).

func (*Installer) Progress

func (i *Installer) Progress() float64

Progress returns the size ratio between already installed files and all files. The result is a float between 0.0 and 1.0, inclusive.

func (*Installer) Rollback

func (i *Installer) Rollback()

Rollback can be used to abort and roll back (i.e. delete) the files and directories that have been installed so far. It will not delete files that haven't been written by the installer, but will delete any file that was overwritten by it.

Rollback implicitly calls Abort().

func (*Installer) SetProgressFunction

func (i *Installer) SetProgressFunction(function func(InstallStatus))

SetProgressFunction takes a function which receives an InstallStatus, and calls it every time right before the installer starts to copy a file or directory.

func (*Installer) SizeString

func (i *Installer) SizeString() string

SizeString returns a human-readable string denoting the total size of all files contained in the installer, appending a size suffix as needed.

func (*Installer) SpaceString

func (i *Installer) SpaceString() string

SpaceString returns a human-readable string denoting the remaining available space on the currently selected installer target path.

func (*Installer) StartCommandAvailable

func (i *Installer) StartCommandAvailable() bool

StartCommandAvailable is queried when deciding whether to install an application- launcher entry, or whether to enable running the application after a successful installation.

func (*Installer) StartInstall

func (i *Installer) StartInstall()

StartInstall runs the installer in a separate goroutine and returns immediately. Use Status() to get updates about the progress.

func (*Installer) WaitForDone

func (i *Installer) WaitForDone()

WaitForDone returns only after the installer has finished installing (or rolling back).

type Translator

type Translator struct {
	Variables VariableMap
	// contains filtered or unexported fields
}

func NewTranslator

func NewTranslator() *Translator

NewTranslator returns a Translator without any variable lookup.

func NewTranslatorVar

func NewTranslatorVar(variables VariableMap) *Translator

NewTranslatorVar returns a Translator with a variable lookup. It scans for any yaml files inside the languages folder in the resources box.

func (*Translator) Expand

func (t *Translator) Expand(str string) (expanded string)

Expand expands template variables in the given str (if any) with the translator's current language's strings.

func (*Translator) Get

func (t *Translator) Get(key string) string

Get returns the localized string for a given string key.

The strings may contain template references to variables, which in turn may contain template references back to message strings. Only one round-trip of string -> variable -> string lookup is performed (i.e. a template variable in a localized string which is used by another template variable will not be expanded and the raw template would appear in the output.)

func (*Translator) GetAll

func (t *Translator) GetAll(key string) VariableMap

GetAll returns a map of all localizations for a given string, indexed by the language code.

func (*Translator) GetAllList

func (t *Translator) GetAllList(key string) (versions []string)

GetAllList returns a flat string list of all localizations for a given string key.

func (*Translator) GetAllStringsRaw

func (t *Translator) GetAllStringsRaw() VariableMap

GetAllStringsRaw returns the unexpanded string map of all strings for the current language.

func (*Translator) GetLanguage

func (t *Translator) GetLanguage() string

GetLanguage returns the identifier (e.g. "en") for the current language.

func (*Translator) GetLanguages

func (t *Translator) GetLanguages() (languages []string)

GetLanguages returns a list of identifiers for all available languages. The default language (if it has strings available) will be the first in the list, the rest is sorted alphabetically.

func (*Translator) SetLanguage

func (t *Translator) SetLanguage(language string) (err error)

SetLanguage given a language code string (e.g.: "en"), sets the translator's language.

type UntypedVariableMap

type UntypedVariableMap map[string]interface{}

UntypedVariableMap is a string-to-interface{} lookup

type VariableMap

type VariableMap map[string]string

VariableMap is string-to-string lookup.

func MergeVariables

func MergeVariables(varMaps ...VariableMap) VariableMap

MergeVariables combines several variable maps into a single one. Duplicate keys will be overridden by the value in the last map which has the key.

Directories

Path Synopsis
+build ignore
+build ignore

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL