1
0
Fork
You've already forked SintesLab-Quickstrap
0
A quick and dirty way to make machines Ansible-ready, created for small businesses (and home labs). Playbooks included.
  • Shell 87.8%
  • Jinja 12.2%
2026年07月11日 20:16:50 +02:00
alt_quickstrap-super feat: add alternate inventory and super-file pickers 2026年07月11日 17:09:05 +02:00
ansible Add missing information to readme, tiny layout correction to 2026年07月11日 20:16:50 +02:00
dir-quickstrap feat: add alternate inventory and super-file pickers 2026年07月11日 17:09:05 +02:00
LICENSES
windows
.editorconfig chore: convert indentation from tabs to spaces 2026年07月06日 21:12:54 +02:00
.gitignore fix: a couple buggy .gitignore entries and .gitignore layout 2026年07月11日 19:20:48 +02:00
CHANGELOG bump v1.2.5 and CHANGELOG 2026年07月11日 19:13:52 +02:00
install.sh less mild cosmetic changes to install.sh 2026年07月11日 17:31:46 +02:00
quickstrap fix: quickstrap update checker edge case with non-linear tag ancestry 2026年07月11日 18:41:02 +02:00
quickstrap-super.example.ini add config file examples 2026年07月07日 03:28:42 +02:00
README.md Add missing information to readme, tiny layout correction to 2026年07月11日 20:16:50 +02:00

Quickstrap

SintesLab - Quickstrap exists with the purpose of bootstrapping a fresh Linux host over SSH, because I am lazy and don't want to do it myself. Now you can be lazy too.

Currently it works with Debian and Ubuntu-based distros. I promise, my mental //TODO has Fedora and RHEL listed. Arch, Gentoo, and LFS may come up after I retire.

When pointed at an IP address, Quickstrap creates an Ansible user, keygens and installs that user's SSH key, hardens sshd so password logins are 'Match' disabled, and optionally sets the hostname and timezone. Once a host has been bootstrapped, Quickstrap can be used to run Ansible playbooks, putting in even less effort than usual.

Who this is for:

  • Homelabbers and systems administrators in small to medium businesses (depending on how you define business size), so essentially guys like me.
  • People who want to read some bash and learn more about it (like myself).

Who this isn't for:

  • Whoever looks at it and thinks "Nah, I don't need this."
  • Anyone whose job description includes taking care of audits or being audited, or runs amounts of hosts which would lead to them caring about the Big O complexity of functions in a bash script, Terraform magicians, or other high-level wizards. You are absolutely welcome to use this, but I really believe you probably do not need to.
  • People who take pride in personally logging onto every host. Mad respect.

A note on my comments:

Unless they are factually incorrect or confusing, assume them to be completely necessary to code execution. Especially the silly ones. Even more so the ones literally explaining functions in words so that even a complete $stupid_variable can skim them quickly. The goal is for the code to be relatively understandable to a new-ish Linux admin and also to the very forgetful and sometimes extremely absent-minded maintainer.

Copyright (C) 2026 SintesLab. Licensed under the GPL-3.0-or-later; see the license header in each script for details.

Contents

Requirements

Control machine (where you run Quickstrap): Linux, WSL, or macOS.

Quickstrap needs ssh, scp, and ssh-keygen on the machine you run it from. You need to also have ansible if you wish to use ansible.

If you do have ansible and wish to use --run-ansible, you may also want to get fzf. It is entirely optional, but makes the interface look better.

Remote host: Debian or Ubuntu, reachable over SSH.

Installation

Clone or download this repo, unless you wish to copy it by hand.

CAUTION: the decision to cleanly retype the whole repo, by the use of a good keyboard, carries serious health risks such as "sudden ASMR".

Run ./install.sh once from the repository root. It makes the shell scripts executable, sets the ownership of the tree on Linux and WSL (macOS is left as-is), and symlinks quickstrap onto your PATH:

  • Linux / WSL: /usr/local/bin/quickstrap (requires root permissions)
  • macOS: ~/.local/bin/quickstrap

You can still run ./quickstrap directly from the repo without installing, but running install.sh is the recommended way so that you don't even need to type ./, or, even worse, XO bash.

Quick start

# Clone the repo
git clone https://codeberg.org/SintesLab/SintesLab-Quickstrap
# Install quickstrap onto your PATH
cd SintesLab-Quickstrap && ./install.sh
# Setup to pick an Ansible username, an SSH port, and a timezone.
# Choosing the timezone does not require you to use it later.
quickstrap --setup
# Bootstrap a single host. You'll be asked for the login user, whether to
# set a hostname and timezone (both optional), and the Ansible sudo password.
quickstrap --send xxx.xxx.x.xx
# Once the machine is bootstrapped, you can run Ansible playbooks 
# against it (depending on the playbook, you may need to set variables first).
quickstrap --run-ansible

If you do not wish to use the scripts interactively, see the [--send fast-track][#quickstrap---send---fast-track] flags below.

Should you have multiple hosts to set up quickly, see the --super section further down. It reads a quickstrap-super.ini file and runs --send with fast-track flags on each one of them.

Directory layout

Quickstrap has (and expects) the following layout:

repo/
├── install.sh # run once to install onto your PATH
├── quickstrap # quickstrap dispatcher
├── uninstall.sh # generated by install.sh; removes the symlink
├── quickstrap-super.ini # optional, only needed for --super
├── quickstrap-super.example.ini 
├── ansible/
│ ├── ansible.cfg
│ ├── alt_hostfiles/ # add any alternative hostfiles here
│ ├── playbooks/ # ansible playbooks
│ ├── roles/
│ ├── group_vars
│ │ └── all.yml # All variables ansible uses are here
│ ├── hosts.example.ini
│ └── ...
├── windows/ # Alloy config template and generator for Windows monitoring targets
├── alt_quickstrap-super/ # you can add alternative quickstrap-super.ini files here
└── dir-quickstrap/
 ├── setup.sh
 ├── send.sh
 ├── super.sh
 ├── ansible.sh
 ├── fast-track.sh
 ├── spinner.sh
 ├── sinteslab-banner.sh
 ├── payload-src/ # scripts staged and copied to the remote host
 │ ├── strapme.sh
 │ ├── progress-bar.sh
 │ └── checkwinsize.sh
 ├── config.example
 ├── config # generated by --setup
 ├── keys/ # generated by --setup, holds SSH keypairs
 ├── backup/ # generated only if a key is regenerated
 ├── runtime/ # transient files during --send
 └── history # timestamped log of every --send run (mode 600)

config, keys/, backup/, runtime/, and history don't exist until you use Quickstrap. uninstall.sh appears after ./install.sh. These are written next to the scripts rather than committed to the repository, since they hold values, keys, and install state specific to your own machine. Ephemeral password and key files written into payload-src/ during --send are also kept out of version control.

Commands

quickstrap --setup

Runs an interactive wizard that asks for an Ansible username and an SSH port, generates an ed25519 keypair for that username under keys/, and lets you pick a timezone by browsing the local /usr/share/zoneinfo tree. The result is written to a config file that other commands read from, so you only need to answer these questions once, unless you wish to change the setup between groups of machines.

If a key already exists for the username you choose, it's backed up to backup/ before a new one is generated, rather than being silently overwritten.

If /usr/share/zoneinfo isn't present or readable (this can happen on some minimal systems), the wizard falls back to letting you continue with UTC or exit so you can install tzdata first, rather than guessing.

quickstrap --send <ip>

Bootstraps a single host at the given IP address. Without any extra flags, it asks you interactively for the login user on the remote host, whether to set the hostname and timezone you chose during setup, and the Ansible sudo password.

Here is what happens once those questions are answered:

  1. An SSH connection is opened once, using SSH's ControlMaster feature. The password prompt, the file copy, and the final session all reuse the same authenticated connection instead of asking you to authenticate three times.

  2. The Ansible password and the public key are written into payload-src/ just long enough to be copied to the remote host, then deleted from the local machine immediately afterward.

    The password file is created at mode 600 before anything is written to it so as not to be readable for other users logged on to your machine.

  3. The payload is copied to /tmp/qs-payload on the remote host with permissions preserved.

  4. strapme.sh runs on the remote host over a full pseudo-TTY. When done, it drops you into an interactive shell session on the remote host, in case you wish to do something extra or inspect the result / log.

Setting the hostname or the timezone on a remote host can break existing infrastructure that depends on the current values, so --send always asks for explicit confirmation before doing either. The only way to skip this confirmation is using the --hostname, --timezone, --no-hostname, or --no-timezone fast-track flags described below.

Run quickstrap --send --help (same bro.. same) to see the full list of fast-track flags.

 --login-user=<user> SSH login user on the remote host. Defaults to root.
 --ansible-user=<user> Ansible username to create. Also generates its keypair.
 --ssh-port=<port> SSH port the remote host is currently listening on.
 --timezone=<tz> IANA timezone to set on the remote host (e.g. Europe/Berlin).
 --hostname=<hostname> Hostname to set on the remote host.
 --no-hostname Skip setting the hostname on the remote host.
 --no-timezone Skip setting the timezone on the remote host.

When any of --ansible-user, --ssh-port, and --timezone are provided, a config file is not required. Defaults are stubbed in memory so you can run --send without having run --setup first.

Every --send run appends a timestamped entry (date, target IP, and remote arguments) to dir-quickstrap/history (created at mode 600 on first use), for easy documentation of what was bootstrapped and when.

quickstrap --super <--single-pass|--multi-pass>

Runs --send in fast-track mode for every host listed in a quickstrap-super.ini file, which must sit next to the quickstrap script itself (see quickstrap-super.example.ini for the exact format).

You can have multiple quickstrap-super.ini files. Create as many .ini files as you wish in the alt_quickstrap-super directory. Passing the --alt-file argument after single-pass or multi-pass will let you pick one of your .ini files from the alt_quickstrap-super dir interactively.

--super exists to make your life easier when you wish to bootstrap a batch of hosts all at once, with the same ansible user and the same or different ansible user sudo pass for each one.

If your login user has key-based ssh auth, the script runs without showing a single prompt in --single-pass mode. And, let's be honest, you should be using key-based auth anyways.

Each section of the INI file is an IP address, and the keys underneath it are the same values --send accepts in the command line.

login_user, ansible_user, and ssh_port are required, while timezone and hostname are optional.

Commenting out a section header (for example, #[1.2.3.4]) skips that host entirely.

If the same IP address appears twice, only the first occurrence is used. In that case, a .ini.deduped copy of the file is written with the later, duplicate section commented out and annotated, as I respect your choices and wouldn't delete your section.

You must choose one of two ways to handle the Ansible sudo password:

  • --single-pass asks for the password once and reuses it for every host in the file.
  • --multi-pass asks for a separate password for each host, all collected up front before any SSH connections are made.

Either way, the password for each host is passed to send.sh through the ANSIBLE_QS_PASS environment variable, which it reads once and unsets immediately afterward.

quickstrap --run-ansible

Opens an interactive picker over the .yml/.yaml files in ansible/playbooks/, using fzf if it's installed or a numbered menu otherwise.

After a playbook is chosen, you're shown a small menu of common ansible-playbook flags (-K, --ask-vault-pass, --step, --limit, and --hostfile) that can be toggled on or off before the playbook runs. --hostfile lets you pick an alternate inventory file from ansible/alt_hostfiles/ instead of the default hosts.ini.

quickstrap --update

Pulls the latest code from the SintesLab-Quickstrap repo, re-runs install.sh to refresh permissions and regenerate uninstall.sh/update.sh, and shows the CHANGELOG.

This always gets you the current tip of main — including any commits that haven't been tagged yet. Run it whenever quickstrap tells you a new release is available, or anytime you just want the latest bleeding-edge code, playbooks included, ahead of the newest tagged release.

Running quickstrap --update unprompted essentially gives you the "nightly" version of quckstrap (..ahem.. not that tags can be called stable).

Update notifications

Every 24 hours, quickstrap quietly checks, when used while you have an internet connection, whether a newer tagged release exists on the remote (not just newer commits on main).

If you're behind, you'll see something like:

A quickstrap update is available (v1.1.0 -> v1.2.0, 4 commit(s) behind):
a1b2c3d feat: add windows target config generator playbook
9f8e7d6 fix: correct ufw rule ordering in prometheus-deb role
Run 'quickstrap --update' to get the latest code.

quickstrap --show-location

If you are goofy like me and forget where you cloned the repo after a while --show-location will tell you where the Sinteslab-Quickstrap dir is on your filesystem.

You can even do something like: cd $(quickstrap --show-location)

The config file

Once --setup has run, its answers live in dir-quickstrap/config as plain KEY=value lines:

ANSIBLE_USERNAME=ansible
SSH_PORT=22
TIMEZONE=UTC

What happens on the remote host (strapme.sh)

strapme.sh is copied to the remote host as part of the payload and does the actual bootstrapping work there, in seven steps:

  • checking internet connectivity
  • creating the Ansible user
  • setting that user's password and immediately shredding the password file
  • installing required packages (python3, python3-apt, python3-debian, curl, git, and ufw unless iptables-persistent or netfilter-persistent is already present)
  • applying the hostname and timezone if requested
  • installing the SSH authorized key
  • hardening sshd so that only key-based authentication is accepted for that user (written to a drop-in file under sshd_config.d/ on modern systems, or appended as a Match User block to sshd_config on older ones; validated with sshd -t before reload)

Every step is logged to bootstrap.log inside the payload directory, and that log is copied to /var/log/quickstrap-bootstrap.log on the remote host before the payload directory is removed.

A note on ansible

I cannot provide a full guide on how to write and use Ansible, but here is a quick rundown of what is relevant to this repository:

ansible/
├── ansible.example.cfg
├── group_vars
│ └── all.example.yml
├── hosts.example.ini
├── playbooks
│ ├── grafana_loki_prometheus_deb.yml
│ ├── linux_server_alloy_setup_deb.yml
│ ├── system_updates-deb.yml
│ └── windows_target_config_generator.yml
└── roles
 ├── alloy-deb
 ├── alloy-docker-deb
 ├── base-deb
 ├── caddy-deb
 ├── grafana-deb
 ├── log
 ├── loki-deb
 ├── prometheus-deb
 └── sudo-detect
  • group_vars/all.yml is where every variable the playbooks and roles read from lives. If you want to change a port, a version, or a path, this is the first file to look at. all.example.yml shows the format.

    !IMPORTANT: the default Ansible user name sits in this file as well, as well as the SSH key location for this user. You can create other host_group.yml files and place further ansible users and ssh key locations based on the host group you have created in your hosts.ini file.

  • ansible.cfg holds the usual Ansible settings: where the inventory lives, where the roles live, and a few defaults so you're not typing -i hosts.ini by hand every time.

  • hosts.ini is the inventory: the list of hosts (and host groups) a playbook actually runs against. See hosts.example.ini for the format.

  • roles/ holds the reusable building blocks. Each one follows the standard Ansible role layout (tasks/, handlers/, templates/, and sometimes defaults/), and is meant to be pulled into one or more playbooks rather than run on its own.

  • playbooks/ holds the actual entry points: each .yml file wires together whichever roles it needs and applies them to a group of hosts from the inventory.

Note that I do not set the Ansible user to use passwordless sudo. This means you need to use -K when running the playbooks unless you do so yourself.

If you decide to use password protected ssh keys it helps to have ssh-agent installed.

ssh-agent bash
ssh-add /whatever/location/your/key/has
exit # When done

You will get prompted way less often.

None of this replaces reading the actual .yml files before you point a playbook at something you care about. Treat Ansible like a loaded gun, especially knowing that I wrote it (just kidding, or AM I??!?).

Final words and security

I have taken some security precautions while making Quickstrap. Credential files are always created with restrictive permissions and cleaned up after having been used, SSH for the new user is hardened a bit, and SSH connections are closed after use.

None of these make Quickstrap a hardened security tool by any stretch of the imagination. It exists purely so that it is convenient to get an Ansible user on a host or small fleet quickly, after which Ansible playbooks are expected to take over.

You will find some basic configuration playbooks in the ansible dir which you can use to add layers to your fresh machine's security. They are, however, just like any security measure, not enough on their own and cannot be exhaustive due to the simple fact that SEC is based on your own threat model and not mine.

If you are using the tool on already properly secured infrastructure, I have nothing to tell you except have fun with it if you don't already have a better way to do what Quickstrap does. I have built the scripts keeping in mind that they may be used on machines that have already been set up. For this reason, I have tried to not take any liberties that may break your setup.

FAQs

I already have an ansible user and playbooks, how can I use them with quickstrap --run-ansible?

  • Copy your existing Ansible user's private ssh key file to the dir-quickstrap/keys directory (and apply chmod 600 to it).

  • Get your hosts.ini file and either place it as the main hosts.ini file or in alt_hostfiles under the ansible/ dir.

  • If you are going to use the same Ansible user you already have for all new hosts you do not really need to do anything else beside make sure this user and its keyfile are set under group_vars/all.yml

  • Otherwise create a group for your hosts in the hosts file you provided (for example [my_hosts]) and put your hosts under it. Then create group_vars/my_hosts.yml and place there the username of your pre-existing Ansible user as well as the keyfile location ( which is now essentially ../dir-quickstrap/keys/thekeyyouhave ).

  • Any of your own playbooks you place under playbooks will be shown in --run-ansible. Make sure that all relevant variables you have are placed in either all.yml or my_hosts.yml or whatever other vars dir or files you may have (keep them all in the ansible dir).

License

SintesLab - Quickstrap Copyright (C) 2026 SintesLab (Kiril Shahamov)

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, version 3 or (at your option) any later version, as published by the Free Software Foundation. It comes with ABSOLUTELY NO WARRANTY. See LICENSES/GPL-3.0-or-later.txt for the full text.

Third-party attribution

Some configuration structure in this project is adapted from grafana/alloy-scenarios, licensed under the Apache License, Version 2.0. See LICENSES/Apache-2.0.txt for the full text.

The spinner, checkwinsize, and progress-bar scripts in dir-quickstrap/payload-src/ are adapted from the amazing Dave Eddy's ysap projects (progress-bar, spinner), licensed under the MIT License. See LICENSES/MIT.txt for the full text.

AI Usage Disclosure

Portions of this project were developed with AI assistance (Claude, Anthropic), in the spirit of the Linux kernel's Assisted-by disclosure convention: AI is treated as a tool, not an author. All AI-assisted output was reviewed, tested, and understood by a human before being included, and the author (Kiril Shahamov) takes full responsibility for the correctness, security (or lack thereof), and licensing of the final code.