Skip to main content

Generate Bash installation scripts from a declarative YAML config for multiple Linux distros

Project description

distroscript

Generate installation scripts for various Linux distributions based on a declarative YAML configuration file.

distroscript [-h] --os OS [--out OUT] config_path

Generate installation scripts from YAML config.

positional arguments:
  config_path  Path to the YAML configuration file.

options:
  -h, --help   show this help message and exit
  --os OS      Target operating system (e.g., 'ubuntu', 'fedora').
  --out OUT    Output shell script file path (optional, defaults to stdout).

[!NOTE] For now, only fedora is fully supported, but I plan to add support for ubuntu, popos and mint in the future.

Installation

pip install distroscript

Development

Install dev dependencies with:

pip install -e ".[dev]"
  • MacOS
brew install python3 pyyaml jsonschema

Schema Validation

The configuration files are validated against a JSON Schema specification (src/schema.json) that defines the structure and constraints for all supported package types and their properties.

The validation happens automatically after reading the YAML file and before processing begins. If validation fails, the script will exit with a detailed error message showing:

  • The path to the invalid field
  • A description of what's wrong

This helps catch configuration errors early, such as:

  • Missing required fields (e.g., url for tar packages)
  • Invalid package type names
  • Incorrect property types (e.g., string instead of array)
  • Unsupported properties for a given package type

Basic Usage

The YAML configuration file should defines the packages to be installed as a key, and it's value is a list of methods to install the package in different packaging systems.

Example:

git:
  - type: apt
    packages:
      - git
  - type: dnf
    packages:
      - git

Then, when generating the installation script, the output will depend on the --os argument provided.

For example, for --os fedora, the generated script will use dnf to install git:

> distroscript.py --os fedora config.yaml
#!/bin/bash

sudo dnf install -y git

For --os ubuntu, it will use apt:

> distroscript.py --os ubuntu config.yaml
#!/bin/bash

sudo apt-get install -y git

Features by Example

Below are simple examples for each major feature.

1. Install a package with different methods per OS

  • By declaring multiple methods under the same package:
git:
  - type: apt
    packages: [git]
  - type: dnf
    packages: [git]

[!TIP] When packages is not included, the package name is assumed to be the same as the key:

git:
  - type: apt
  - type: dnf

[!TIP] When declaring a simple string, it is expanded as the type property:

git:
  - apt
  - dnf

2. Add dependencies between packages

  • By declaring another package:
pip:
  - type: apt
    packages:
      - python3-pip
    depends_on:
      - zsh

zsh: [apt]
  • By inlining the dependency as another Package:
pip:
  - type: apt
    packages: [python3-pip]
    depends_on:
      - type: apt
        packages: [zsh]

[!WARNING] If dependency is not declared, distroscript will output a TODO comment, so the user can manually add it to the configuration file later if needed.

3. Run commands before or after install

  • All packages could define pre_install and post_install steps as a list of Commands to be executed before or after the installation.
zsh:
  - type: apt
    pre_install: [...]
    post_install: [...]
  • Type shell inlines the content into the destination script:
zsh:
  - type: apt
    post_install:
      - type: shell
        content: |
          chsh -s "$(which zsh)" "$USER"

[!TIP] It's the default in case we provide a simple string:

zsh:
  - type: apt
    post_install: |
      chsh -s "$(which zsh)" "$USER"
  • Type tee writes content to a file, useful to add desktop entries or config files:
kazam:
  - type: pip
    post_install:
      - type: tee
        destination: "$HOME/.local/share/applications/kazam.desktop"
        content: |
          [Desktop Entry]
          Name=Kazam
          Exec=kazam
          ...

[!TIP] By default, it overwrites the file, but if append: true is set, it appends to the file so existing content is preserved:

zsh:
  - type: apt
    post_install:
      - type: tee
        destination: "$HOME/.zshrc"
        append: true
        content: |
          # pip
          export PATH="$HOME/.local/bin:$PATH"

[!TIP] You can also specify sudo: true to write the file to a destination that requires elevated permissions:

dnf-automatic:
  - type: dnf
    post_install:
      - type: tee
        destination: /etc/dnf/automatic.conf
        sudo: true
        content: |
          [commands]
          apply_updates=True

[!TIP] You can specify mkdir: true to create the destination directory before writing the file:

kazam:
  - type: pip
    post_install:
      - type: tee
        destination: "$HOME/.local/share/applications/kazam.desktop"
        mkdir: true
        content: |
          [Desktop Entry]
          Name=Kazam
          Exec=kazam
          ...

4. Install from Flatpak, Snap, Pip, Tar, Zip, GitHub, File, Shell, or AppImage

For these types, if specified --os does not include the required command, it will be included as a dependency, as if we declared it in depends_on as a single string.

This allows the user to provide an installation method for the package manager itself if needed.

Flatpak:

kdenlive:
  - type: flatpak
    packages: [org.kde.kdenlive]
    # depends_on: [flatpak] # Added implicitly if running on an OS without flatpak pre-installed

Snap:

code:
  - type: snapd
    packages: [code]
    classic: true # Optional: adds the --classic flag
    # depends_on: [snapd] # Added implicitly if running on an OS without snapd pre-installed

Pip:

yt-dlp:
  - type: pip
    packages: ["yt-dlp[default]"]
    # depends_on: [pip] # Added implicitly if running on an OS without pip pre-installed

Tar:

go:
  - type: tar
    url: "https://go.dev/dl/go1.20.5.linux-amd64.tar.gz"
    destination: /usr/local
    sudo: true # Optional: use sudo to extract to destination

Zip:

jetbrains-nf:
  - type: zip
    url: "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/JetBrainsMono.zip"
    destination: /usr/share/fonts/JetBrainsMonoNerdFont
    sudo: true # Optional: use sudo to extract to destination

GitHub:

cspec:
  - type: github
    repository: mumuki/cspec
    install: |
      make
      sudo make install

File:

doctest:
  - type: file
    url: https://raw.githubusercontent.com/doctest/doctest/v2.4.12/doctest/doctest.h
    destination: /usr/local/include/doctest/doctest.h
    sudo: true # Optional: use sudo to write to destination
    silent: true # Optional: suppress output after download
    executable: false # Optional: make the file executable after download

Shell:

  • By providing an url:
rust:
  - type: shell
    url: https://sh.rustup.rs
    shell: bash # Optional: specify the shell to use (default: bash).
                # Will be added as a dependency if not preinstalled in the target OS.
  • By providing an inline script:
oh-my-zsh:
  - type: shell
    script: |
      sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    shell: zsh # Optional: specify the shell to use (default: bash).
               # Will be added as a dependency if not preinstalled in the target OS.

AppImage:

obsidian:
  - type: appimage
    url: https://github.com/obsidianmd/obsidian-releases/releases/download/v1.8.10/Obsidian-1.8.10.AppImage
    name: Obsidian
    icon_name: obsidian
    mime_types:
      - x-scheme-handler/obsidian
      - type: text/markdown
        default: true

AppImages are downloaded to $HOME/.local/bin/, a desktop entry is created in $HOME/.local/share/applications/, and icons are extracted automatically when icon_name is specified.

When mime_types is provided, the desktop entry advertises those MIME types. If default is not set, only scheme handler strings register the AppImage as their default handler, other MIME types will not be set as default unless default: true is explicitly specified.

[!NOTE] AppImage packages require the url field. The name, icon_name, categories, and mime_types fields are optional. The name field defaults to the package name, icon_name enables desktop icon extraction when provided, and categories defaults to "Application".

5. Use custom flags for package managers

gh:
  - type: dnf
    flags:
      - "--repo gh-cli"
    pre_install: |
      sudo dnf config-manager addrepo --from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo

gleam:
  - type: dnf
    copr: frostyx/gleam
    pre_install: |
      sudo dnf copr enable frostyx/gleam -y

[!TIP] For dnf, you can also specify repo, repofile and/or copr properties:

gh:
  - type: dnf
    repo: gh-cli
    repofile: https://cli.github.com/packages/rpm/gh-cli.repo

gleam:
  - type: dnf
    copr: frostyx/gleam

6. Add desktop entries or config files

kazam:
  - type: pip
    post_install:
      - type: tee
        destination: "$HOME/.local/share/applications/kazam.desktop"
        content: |
          [Desktop Entry]
          Name=Kazam
          Exec=kazam
          ...

7. Merging multiple packages to a single install command

If multiple packages use the same installation method and are not codependant, they will be merged into a single command to optimize the installation process.

jq: [apt]

htop: [apt]

zsh: [apt]

pip:
  - type: apt
    packages: [python3-pip]
    depends_on: [zsh]

Output:

#!/bin/bash

sudo apt-get install -y jq htop zsh

sudo apt-get install -y python3-pip

8. Combine everything for complex setups

docker:
  - type: dnf
    packages:
      - docker
      - docker-ce
      - docker-ce-cli
      - containerd.io
      - docker-buildx-plugin
      - docker-compose-plugin
    repofile: https://download.docker.com/linux/fedora/docker-ce.repo
    depends_on:
      - type: dnf
        packages: [dnf5-plugins]
    post_install: |
      sudo systemctl enable --now docker
      sudo groupadd -f docker
      sudo usermod -aG docker "$USER"

Output:

#!/bin/bash

sudo dnf install -y dnf5-plugins docker docker-ce docker-ce-cli \
  containerd.io docker-buildx-plugin docker-compose-plugin

sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

sudo systemctl enable --now docker
sudo groupadd -f docker
sudo usermod -aG docker "$USER"

License

This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.

Author

Agustin Ranieri - @RaniAgus

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

distroscript-0.2.1.tar.gz (20.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

distroscript-0.2.1-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file distroscript-0.2.1.tar.gz.

File metadata

  • Download URL: distroscript-0.2.1.tar.gz
  • Upload date:
  • Size: 20.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for distroscript-0.2.1.tar.gz
Algorithm Hash digest
SHA256 bd3041978f05159f85d87918c009ec063773dd3a42530a76e2b1992792dc8541
MD5 d4e95a2a2cf218d78573c1585e17398c
BLAKE2b-256 d52a20cd70934eab7a32ace4494987c129bda4d5bb3d154ac1c80697c30daf14

See more details on using hashes here.

Provenance

The following attestation bundles were made for distroscript-0.2.1.tar.gz:

Publisher: publish.yml on RaniAgus/distroscript

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file distroscript-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: distroscript-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for distroscript-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6bfb3de80c117e5aef7e0060b994703b5e330f004fbf6ec879fbe3f03f797d27
MD5 71cde08834541733beab8d5a6c8b4856
BLAKE2b-256 77641739387877abb8fd6f05b3b92284c6e7f56ea751e8fd171222902666efae

See more details on using hashes here.

Provenance

The following attestation bundles were made for distroscript-0.2.1-py3-none-any.whl:

Publisher: publish.yml on RaniAgus/distroscript

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page