Initial
This commit is contained in:
commit
6defb43dfa
29 changed files with 813 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
vault.key
|
168
LICENSE
Normal file
168
LICENSE
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
Copyright (c) 2019 Sutty
|
||||||
|
|
||||||
|
The following license is modified from the MIT license and downloaded
|
||||||
|
from <https://github.com/Laurelai/anti-fascist-mit-license> on
|
||||||
|
2019-07-11.
|
||||||
|
|
||||||
|
Anti-Fascist MIT License:
|
||||||
|
|
||||||
|
The following conditions must be met by any person obtaining a copy of
|
||||||
|
this software:
|
||||||
|
|
||||||
|
- You MAY NOT be a fascist.
|
||||||
|
- You MUST not financially support fascists.
|
||||||
|
- You MUST not intentionally provide or knowingly provide through
|
||||||
|
inaction a platform for fascists to spread propaganda or organize.
|
||||||
|
- You MUST not publicly voice support for fascists.
|
||||||
|
- You MAY NOT be a member of any fascist organization, even if you are a
|
||||||
|
member to infiltrate for anti-fascist purposes.
|
||||||
|
|
||||||
|
"Fascist" can be understood as any group or individual who promotes the
|
||||||
|
political ideology of fascism.
|
||||||
|
|
||||||
|
"Fascism" can be broken down into 11 ideological features as well as 8
|
||||||
|
tactics that can form a fascist system in varying combinations, for the
|
||||||
|
sake of simplicity and brevity the individual or organization in
|
||||||
|
question must match to at least 5 features or tactics or a combination
|
||||||
|
of the two determined by the individual licencer.
|
||||||
|
|
||||||
|
Said licencer may provide a list if an individual or group matches to at
|
||||||
|
least 5 features upon request from the individual or group in question.
|
||||||
|
|
||||||
|
The ideological features are listed below.
|
||||||
|
|
||||||
|
1. Hyper-nationalism.
|
||||||
|
|
||||||
|
As defined as "The belief in the superiority of one's nation and of the
|
||||||
|
paramount importance of advancing it."
|
||||||
|
|
||||||
|
2. Militarism.
|
||||||
|
|
||||||
|
As defined as "Advocating for an increase in military forces beyond what
|
||||||
|
the real defense of a nation needs, more influence of the military upon
|
||||||
|
the policies of the civilian government, and a preference for force as a
|
||||||
|
solution over diplomacy for problems."
|
||||||
|
|
||||||
|
3. Glorification of violence and readiness to use it in politics.
|
||||||
|
|
||||||
|
As defined as "The belief that violence can be used to cleanse a
|
||||||
|
tarnished nation, also by using violence to harm, intimidate or kill
|
||||||
|
political oppoenents."
|
||||||
|
|
||||||
|
4. Fetishization of youth.
|
||||||
|
|
||||||
|
As defined as "Extolling the virtues of youth and making a special
|
||||||
|
appeal to young people to join a cause or organization"
|
||||||
|
|
||||||
|
5. Fetishization of masculinity.
|
||||||
|
|
||||||
|
As defined as "Extolling the virtues of male authority or patriarchy and
|
||||||
|
making a special appeal to men to be leaders of households and groups"
|
||||||
|
|
||||||
|
6. Leader cult.
|
||||||
|
|
||||||
|
As defined as "Creating an idealized, heroic, and worshipful image of a
|
||||||
|
leader, often through unquestioning flattery and praise."
|
||||||
|
|
||||||
|
7. Lost-golden-age syndrome.
|
||||||
|
|
||||||
|
As defined as "Creating or promoting the idea that a nation had a lost
|
||||||
|
or stolen golden age in the past that must be returned to"
|
||||||
|
|
||||||
|
8. Self-definition by opposition.
|
||||||
|
|
||||||
|
As defined as "Creating or promoting the idea that the group or
|
||||||
|
individual is the only person or way who can fight real or imagined
|
||||||
|
evils within a society."
|
||||||
|
|
||||||
|
9. Mass mobilization and mass party.
|
||||||
|
|
||||||
|
As defined as "Creating or promoting the creation of a populist group or
|
||||||
|
party for the advancment of fascist tactics or features."
|
||||||
|
|
||||||
|
10. Hierarchical party structure and tendency to purge the disloyal.
|
||||||
|
|
||||||
|
As defined as "Removal of membership from a group for lacking absolute
|
||||||
|
loyalty or lacking further usefulness to the group. Also having a
|
||||||
|
hierarchical structure within the group itself."
|
||||||
|
|
||||||
|
11. Theatricality.
|
||||||
|
|
||||||
|
As defined as "Using spectacle to gain and keep the attention of those
|
||||||
|
inside and outside of the group using speeches full of absolutes and or
|
||||||
|
superlatives. Elaborate collective rituals (rallies) meant to reenforce
|
||||||
|
loyalty within the group."
|
||||||
|
|
||||||
|
Fascist tactics include
|
||||||
|
|
||||||
|
1) Persecution of national minorities.
|
||||||
|
2) Persecution of racial minorities.
|
||||||
|
3) Persecution of religious minorities (Anti-Semitism, Islamophobia and others).
|
||||||
|
4) Promotion of a type of national purity.
|
||||||
|
5) Promotion of a state run by ideologically oriented corporate bodies.
|
||||||
|
6) Persecution of gender or sexual minorities.
|
||||||
|
7) Persecution of the disabled.
|
||||||
|
8) Formation of extra-legal forces (brownshirts) to defend fascist values.
|
||||||
|
|
||||||
|
Special criteria: Meeting only one point of the special criteria is
|
||||||
|
enough to consider someone or a group to be fascist for the purposes of
|
||||||
|
this licence.
|
||||||
|
|
||||||
|
1. Promotion of any theories that state members of the jewish ethnicity
|
||||||
|
or faith control or largely control the world, finance, or other
|
||||||
|
global major power system.
|
||||||
|
|
||||||
|
2. Denial of the holocaust or any other historically proven genocide.
|
||||||
|
|
||||||
|
3. Promotion of ethnostates.
|
||||||
|
|
||||||
|
4. Advocating for eugenics. Either positive or negative eugenics.
|
||||||
|
Promotion for the rights of abortion are not considered eugenics.
|
||||||
|
|
||||||
|
5. Advocating for the removal of rights or legal protections from a
|
||||||
|
class or group of people.
|
||||||
|
|
||||||
|
Former fascists: People or organizations who used to promote the
|
||||||
|
political ideology of fascism but no longer do so must meet the
|
||||||
|
following criterea to be able to use this software.
|
||||||
|
|
||||||
|
1. Publicly disavow past fascist deeds and ideologies.
|
||||||
|
|
||||||
|
2. Expose any and all known fascists former allies to the public.
|
||||||
|
|
||||||
|
A suggested route would be through the one peoples project
|
||||||
|
(onepeoplesproject.com). If they can confirm you have done so that
|
||||||
|
will count as meeting condition two.
|
||||||
|
|
||||||
|
3. Publicly destroy any and all fascist paraphenelia you have in your
|
||||||
|
posession including removal of tattoos and body markings
|
||||||
|
affiliated with fascist groups or gangs.
|
||||||
|
|
||||||
|
ANTI-FASCIST-MIT LICENSE:
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
The above licence agreement conditions are met in full.
|
||||||
|
|
||||||
|
The Anti-Fascist MIT License may only be used under the terms of the
|
||||||
|
Anti-Fascist MIT License.
|
||||||
|
|
||||||
|
Any modified versions of this software must also include the
|
||||||
|
Anti-Fascist MIT Licence.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
Makefile
Normal file
24
Makefile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Password size
|
||||||
|
size ?= 256
|
||||||
|
# Password
|
||||||
|
password ?= $(shell pwgen -1s $(size))
|
||||||
|
|
||||||
|
alpines: inventory.yml
|
||||||
|
ansible-playbook -i $< --vault-pass-file vault.key alpines.yml
|
||||||
|
|
||||||
|
dockers: inventory.yml
|
||||||
|
ansible-playbook -i $< --vault-pass-file vault.key dockers.yml
|
||||||
|
|
||||||
|
inventory: inventory.yml
|
||||||
|
ansible-inventory -i $< --list
|
||||||
|
|
||||||
|
encrypt-string: vault.key
|
||||||
|
@echo name=\"$(name)\" is the variable name
|
||||||
|
@test -n "$(name)"
|
||||||
|
@echo host=\"$(host)\" is a host file
|
||||||
|
@test -f "host_vars/$(host).yml"
|
||||||
|
@ansible-vault encrypt_string "$(password)" --name "$(name)" --vault-pass-file $< >> "host_vars/$(host).yml"
|
||||||
|
|
||||||
|
vault.key:
|
||||||
|
@echo Creating a vault password on $@. Keep this file safe!
|
||||||
|
@echo -n "$(password)" > $@
|
198
README.md
Normal file
198
README.md
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
# Alpine
|
||||||
|
|
||||||
|
An Ansible playbook for installing an encrypted and compressed data
|
||||||
|
Alpine container host.
|
||||||
|
|
||||||
|
The playbook should run from any live Linux system with network, SSH and
|
||||||
|
a Python interpreter. The goal is to install the host even if the VPS
|
||||||
|
provider doesn't support Alpine officially.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Root (`/`) and boot (`/boot`) directories are unencrypted.
|
||||||
|
|
||||||
|
Storage (`/srv`), including Docker volumes and static files, are
|
||||||
|
encrypted using plain dm-crypt. With plain encryption you don't need to
|
||||||
|
keep dumped LUKS headers around. Just use a sufficiently long
|
||||||
|
passphrase and don't lose it.
|
||||||
|
|
||||||
|
Both root and data are Btrfs partitions with zstd compression enabled.
|
||||||
|
|
||||||
|
After boot, you can open the `/srv` partition and start Docker. This is
|
||||||
|
a manual process so far.
|
||||||
|
|
||||||
|
`/var/log` is mounted in memory, so logs are lost after rebooting.
|
||||||
|
We're working towards sending every log to a remote database using
|
||||||
|
syslog-ng, but OpenRC would need to be replaced, most likely by Monit.
|
||||||
|
|
||||||
|
### Why isn't LUKS used?
|
||||||
|
|
||||||
|
We've had really bad experiences with LUKS2 encryption, where data was
|
||||||
|
completely lost even when we had external header dumps. The only reply
|
||||||
|
we had about this on the dm-crypt list is that LVM+LUKS setups are
|
||||||
|
discouraged.
|
||||||
|
|
||||||
|
With plain encryption we don't have to keep extra stuff around, and
|
||||||
|
according to some obscure comments on dm-crypt documentation, provides
|
||||||
|
a 1:1 mapping between blocks, so hypothetically SSD storage can perform
|
||||||
|
trimming.
|
||||||
|
|
||||||
|
### Why encrypting everything except root?
|
||||||
|
|
||||||
|
To be able to fully encrypt a VM we would need to use GRUB2 with a LUKS1
|
||||||
|
`/boot`. This is possible to do on a physical machine, where you can
|
||||||
|
type the password somewhat comfortably, but on a remote VM this would
|
||||||
|
need to be done over VNC.
|
||||||
|
|
||||||
|
Arguably the host operator can access the VM's RAM so it's game over
|
||||||
|
from the start, but we still want to keep data encrypted at rest.
|
||||||
|
|
||||||
|
## Setting the remote hosts
|
||||||
|
|
||||||
|
Boot any live system ISO. This playbook is tested with latest
|
||||||
|
Archlinux.
|
||||||
|
|
||||||
|
Network configuration should be automatic, but sometimes you need to do
|
||||||
|
it manually, specially if the provider doesn't support DHCP or the
|
||||||
|
gateway is outside the netmask, since it confuses some DHCP clients.
|
||||||
|
|
||||||
|
The Python interpreter is required to run Ansible and you also need
|
||||||
|
`parted` to partitioning the disk.
|
||||||
|
|
||||||
|
Once booted, change root password on the VNC console:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "root:something you can type comfortably over VNC" | chpasswd -
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you can login via SSH via `root`. This is by default in
|
||||||
|
Archlinux.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep PermitRootLogin /etc/ssh/sshd_config
|
||||||
|
```
|
||||||
|
|
||||||
|
If it's not set to `yes`, edit the file and restart `sshd`.
|
||||||
|
|
||||||
|
## Setting the local machine
|
||||||
|
|
||||||
|
Clone this repository.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Projects/Sutty
|
||||||
|
git clone https://gitea.sutty.coop.ar/Sutty/ansible-alpine-host.git
|
||||||
|
cd ansible-alpine-host
|
||||||
|
```
|
||||||
|
|
||||||
|
Install dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For Arch and Arch-base distros
|
||||||
|
pacman -Sy ansible make pwgen
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy your SSH public key to remote hosts so Ansible (and you) can login
|
||||||
|
without a password.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh-copy-id root@your.host.name
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring the playbook
|
||||||
|
|
||||||
|
Create a vault password:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make vault.key
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important:** Since the `vault.key` file contains the password for
|
||||||
|
> your vault, **don't commit this into git** and keep a copy on your
|
||||||
|
> password storage (KeepassXC is a good option). **Always make
|
||||||
|
> backups!**.
|
||||||
|
|
||||||
|
### Inventory
|
||||||
|
|
||||||
|
Add your hostnames into the `inventory.yml` file. You can add host
|
||||||
|
variables here or on separate files in the `host_vars/` directory,
|
||||||
|
following [Ansible
|
||||||
|
documentation](https://docs.ansible.com/ansible/latest/network/getting_started/first_inventory.html).
|
||||||
|
|
||||||
|
We use `host_vars/`, so after adding a hostname, create a correspoding
|
||||||
|
file.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory.yml
|
||||||
|
alpines:
|
||||||
|
hosts:
|
||||||
|
your.host.name:
|
||||||
|
another.host.name:
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# host_vars/your.host.name.yml
|
||||||
|
---
|
||||||
|
# If you don't have DNS yet, set it to the IP address
|
||||||
|
ansible_host: "192.50.248.13"
|
||||||
|
# This isn't used but it's good to have it at hand
|
||||||
|
gateway: "192.99.148.254"
|
||||||
|
netmask: "32"
|
||||||
|
# Optional IPv6 configuration.
|
||||||
|
ip6: "2607:5300:60:555a::4:0"
|
||||||
|
gateway6: "2607:5300:60:55ff:ff:ff:ff:ff"
|
||||||
|
netmask6: "112"
|
||||||
|
# Device where the installation will be performed. If using VirtIO,
|
||||||
|
# it'll probably be `vda`
|
||||||
|
disk_device: "/dev/vda"
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, add an encryption key for the `/srv` partition and a password
|
||||||
|
for the `root` user.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make encrypt-string host="your.host.name" name="key"
|
||||||
|
make encrypt-string host="your.host.name" name="root"
|
||||||
|
```
|
||||||
|
|
||||||
|
Do all this once per host.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Once everything's done, run `make`. You can probably run it several
|
||||||
|
times in a row and stuff shouldn't break (this is called idempotency).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make alpines
|
||||||
|
```
|
||||||
|
|
||||||
|
> We use `make` so we don't have to remember a lot of flags. You can
|
||||||
|
> see what's happening.
|
||||||
|
|
||||||
|
## Post-install
|
||||||
|
|
||||||
|
Once the hosts are installed and rebooted, you can open their `/srv`
|
||||||
|
partitions and start the Docker service.
|
||||||
|
|
||||||
|
Move the hostname from the `alpines` group into the `dockers` group in
|
||||||
|
your `inventory.yml`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
alpines:
|
||||||
|
hosts:
|
||||||
|
- your.host.name:
|
||||||
|
dockers:
|
||||||
|
hosts:
|
||||||
|
+ your.host.name:
|
||||||
|
```
|
||||||
|
|
||||||
|
And run the `make` rule:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make dockers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
We test this over VMs managed via `virt-manager`. Follow the GUI for
|
||||||
|
creating a VM of type Generic OS, load an ISO (we use Archlinux) and
|
||||||
|
create a virtual hard drive of at least 1.1GB.
|
33
alpines.yml
Normal file
33
alpines.yml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
- hosts: "alpines"
|
||||||
|
remote_user: "root"
|
||||||
|
strategy: "free"
|
||||||
|
vars:
|
||||||
|
packages:
|
||||||
|
- alpine-base
|
||||||
|
- linux-virt
|
||||||
|
- syslinux
|
||||||
|
- cryptsetup
|
||||||
|
- btrfs-progs
|
||||||
|
- openssh-server
|
||||||
|
- docker
|
||||||
|
- docker-py
|
||||||
|
- syslog-ng
|
||||||
|
- syslog-ng-openrc
|
||||||
|
- ipset
|
||||||
|
- ipset-openrc
|
||||||
|
- iptables
|
||||||
|
- ip6tables
|
||||||
|
- iptables-openrc
|
||||||
|
- tinc
|
||||||
|
- prometheus-node-exporter
|
||||||
|
- prometheus-node-exporter-openrc
|
||||||
|
tasks:
|
||||||
|
- include_tasks: "tasks/partition.yml"
|
||||||
|
- include_tasks: "tasks/encrypt.yml"
|
||||||
|
- include_tasks: "tasks/format.yml"
|
||||||
|
- include_tasks: "tasks/mount.yml"
|
||||||
|
- include_tasks: "tasks/install.yml"
|
||||||
|
- include_tasks: "tasks/chroot.yml"
|
||||||
|
- include_tasks: "tasks/bootloader.yml"
|
||||||
|
- include_tasks: "tasks/post_install.yml"
|
7
dockers.yml
Normal file
7
dockers.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
- hosts: "dockers"
|
||||||
|
remote_user: "root"
|
||||||
|
strategy: "free"
|
||||||
|
tasks:
|
||||||
|
- include_tasks: "tasks/encrypt.yml"
|
||||||
|
- include_tasks: "tasks/docker.yml"
|
8
host_vars/your.host.name.yml
Normal file
8
host_vars/your.host.name.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
ansible_host: "1.1.1.2"
|
||||||
|
gateway: "1.1.1.1"
|
||||||
|
netmask: "32"
|
||||||
|
ip6: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0"
|
||||||
|
gateway6: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
|
||||||
|
netmask6: "128"
|
||||||
|
disk_device: "/dev/vda"
|
6
inventory.yml
Normal file
6
inventory.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
alpines:
|
||||||
|
hosts:
|
||||||
|
your.host.name:
|
||||||
|
dockers:
|
||||||
|
hosts:
|
14
tasks/bootloader.yml
Normal file
14
tasks/bootloader.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
- name: "Install syslinux bootloader"
|
||||||
|
template:
|
||||||
|
src: "templates/update-extlinux.conf.j2"
|
||||||
|
dest: "/mnt/etc/update-extlinux.conf"
|
||||||
|
mode: "0644"
|
||||||
|
- name: "Update configuration"
|
||||||
|
shell: "PATH=/usr/bin:/usr/sbin:/bin:/sbin chroot /mnt /sbin/update-extlinux"
|
||||||
|
- name: "Install bootloader in MBR. This is how `setup-disk` does it."
|
||||||
|
shell: "cat /mnt/usr/share/syslinux/mbr.bin > {{ disk_device }}"
|
||||||
|
- name: "Install syslinux configuration into /boot using chroot."
|
||||||
|
shell: "PATH=/usr/bin:/usr/sbin:/bin:/sbin chroot /mnt /sbin/extlinux --install /boot"
|
||||||
|
- name: "Generate initramfs."
|
||||||
|
shell: "PATH=/usr/bin:/usr/sbin:/bin:/sbin chroot /mnt /bin/sh -c 'ls /lib/modules | xargs /sbin/mkinitfs -K'"
|
20
tasks/chroot.yml
Normal file
20
tasks/chroot.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
- name: "Create a chroot. Bind mount /dev into /mnt."
|
||||||
|
mount:
|
||||||
|
state: "mounted"
|
||||||
|
src: "/dev"
|
||||||
|
path: "/mnt/dev"
|
||||||
|
opts: "bind"
|
||||||
|
fstype: "none"
|
||||||
|
- name: "Mount /sys into /mnt."
|
||||||
|
mount:
|
||||||
|
state: "mounted"
|
||||||
|
src: "sys"
|
||||||
|
path: "/mnt/sys"
|
||||||
|
fstype: "sysfs"
|
||||||
|
- name: "Mount /proc into /mnt."
|
||||||
|
mount:
|
||||||
|
state: "mounted"
|
||||||
|
src: "proc"
|
||||||
|
path: "/mnt/proc"
|
||||||
|
fstype: "proc"
|
17
tasks/docker.yml
Normal file
17
tasks/docker.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
- name: "Prepare /srv to encrypt Docker files."
|
||||||
|
file:
|
||||||
|
state: "directory"
|
||||||
|
path: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- "/srv/docker"
|
||||||
|
- "/var/lib/docker"
|
||||||
|
- name: "Bind mount /srv/docker to /var/lib/docker."
|
||||||
|
mount:
|
||||||
|
state: "mounted"
|
||||||
|
src: "/srv/docker"
|
||||||
|
path: "/var/lib/docker"
|
||||||
|
opts: "bind"
|
||||||
|
fstype: "none"
|
||||||
|
- name: "Start Docker service."
|
||||||
|
shell: "/etc/init.d/docker start"
|
5
tasks/encrypt.yml
Normal file
5
tasks/encrypt.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
- name: "Open the srv partition with an encryption key. We use plain encryption because it's the simplest, doesn't require to keep luksHeader backups and it's hypothecally friendlier with SSD disks."
|
||||||
|
shell: "echo -n {{ key }} | cryptsetup open --type=plain --allow-discards --key-file - --cipher aes-xts-plain64 --key-size 512 {{ disk_device }}3 srv"
|
||||||
|
args:
|
||||||
|
creates: "/dev/mapper/srv"
|
16
tasks/format.yml
Normal file
16
tasks/format.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
- name: "Use an older and well stablished filesystem format for /boot, so bootloaders can open them."
|
||||||
|
filesystem:
|
||||||
|
dev: "{{ disk_device }}1"
|
||||||
|
fstype: "ext2"
|
||||||
|
state: "present"
|
||||||
|
- name: "Use BTRFS for root."
|
||||||
|
filesystem:
|
||||||
|
dev: "{{ disk_device }}2"
|
||||||
|
fstype: "btrfs"
|
||||||
|
state: "present"
|
||||||
|
- name: "And also srv, which is encrypted underneath"
|
||||||
|
filesystem:
|
||||||
|
dev: "/dev/mapper/srv"
|
||||||
|
fstype: "btrfs"
|
||||||
|
state: "present"
|
54
tasks/install.yml
Normal file
54
tasks/install.yml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
- name: "Download and install APK static into the host system."
|
||||||
|
shell: "cd / && curl http://dl-cdn.alpinelinux.org/alpine/v3.15/main/x86_64/apk-tools-static-2.12.7-r3.apk | tar -xvzf - sbin/apk.static"
|
||||||
|
args:
|
||||||
|
creates: "/sbin/apk.static"
|
||||||
|
- name: "Perform installation in a mostly secure way. First install keys insecurely."
|
||||||
|
shell: "apk.static -X http://dl-cdn.alpinelinux.org/alpine/v3.15/main --root /mnt --arch x86_64 --initdb --allow-untrusted add alpine-keys"
|
||||||
|
args:
|
||||||
|
creates: "/mnt/etc/apk/keys"
|
||||||
|
- name: "Enable repositories."
|
||||||
|
template:
|
||||||
|
src: "templates/repositories.j2"
|
||||||
|
dest: "/mnt/etc/apk/repositories"
|
||||||
|
mode: "0600"
|
||||||
|
- name: "Install packages with signature verification. Update if already present."
|
||||||
|
shell: "apk.static --root /mnt --arch x86_64 add {{ item }}"
|
||||||
|
loop: "{{ packages }}"
|
||||||
|
- name: "Tell Alpine how to mount partitions after boot."
|
||||||
|
template:
|
||||||
|
src: "templates/fstab.j2"
|
||||||
|
dest: "/mnt/etc/fstab"
|
||||||
|
mode: "0755"
|
||||||
|
- name: "Load BTRFS module on boot"
|
||||||
|
shell: "grep -q btrfs /mnt/etc/modules || echo btrfs >> /mnt/etc/modules"
|
||||||
|
- name: "And which features to include into initramfs."
|
||||||
|
template:
|
||||||
|
src: "templates/mkinitfs.conf.j2"
|
||||||
|
dest: "/mnt/etc/mkinitfs/mkinitfs.conf"
|
||||||
|
mode: "0750"
|
||||||
|
- name: "Copy SSH host keys from the live system."
|
||||||
|
shell: "cp -a /etc/ssh/*_key* /mnt/etc/ssh/"
|
||||||
|
args:
|
||||||
|
creates: "/mnt/etc/ssh/ssh_host_ed25519_key"
|
||||||
|
- name: "Copy SSH authorized keys from the live system. First create the /root/.ssh directory."
|
||||||
|
file:
|
||||||
|
state: "directory"
|
||||||
|
path: "/mnt/root/.ssh"
|
||||||
|
owner: "root"
|
||||||
|
group: "root"
|
||||||
|
mode: "0700"
|
||||||
|
- name: "And then the authorized_keys file."
|
||||||
|
shell: "install -m 600 -o root -g root /root/.ssh/authorized_keys /mnt/root/.ssh/authorized_keys"
|
||||||
|
args:
|
||||||
|
creates: "/mnt/root/.ssh/authorized_keys"
|
||||||
|
- name: "Install network configuration."
|
||||||
|
template:
|
||||||
|
src: "templates/interfaces.j2"
|
||||||
|
dest: "/mnt/etc/network/interfaces"
|
||||||
|
- name: "And DNS resolvers."
|
||||||
|
template:
|
||||||
|
src: "templates/resolv.conf.j2"
|
||||||
|
dest: "/mnt/etc/resolv.conf"
|
||||||
|
- name: "Set hostname."
|
||||||
|
shell: "echo {{ inventory_hostname }} > /etc/hostname"
|
19
tasks/mount.yml
Normal file
19
tasks/mount.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
- name: "Mount everything in order so it's ready for the installation later. Root is BTRFS with opportunistic compression. Same for srv. The other options reduce writes and perform SSD cleanup and write optimization."
|
||||||
|
mount:
|
||||||
|
path: "/mnt"
|
||||||
|
src: "{{ disk_device }}2"
|
||||||
|
opts: "compress=zstd,noatime,nodiratime,lazytime,discard"
|
||||||
|
state: "mounted"
|
||||||
|
fstype: "btrfs"
|
||||||
|
- name: "Directories need to be created before mounting can happen."
|
||||||
|
file:
|
||||||
|
state: "directory"
|
||||||
|
path: "/mnt/boot"
|
||||||
|
- name: "Mount /boot. There's no need to mount /srv during install."
|
||||||
|
mount:
|
||||||
|
path: "/mnt/boot"
|
||||||
|
src: "{{ disk_device }}1"
|
||||||
|
opts: "noatime,nodiratime,lazytime"
|
||||||
|
state: "mounted"
|
||||||
|
fstype: "ext2"
|
22
tasks/partition.yml
Normal file
22
tasks/partition.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
- name: "Create a partition for /boot with room enough for kernel and initramfs"
|
||||||
|
parted:
|
||||||
|
device: "{{ disk_device }}"
|
||||||
|
number: 1
|
||||||
|
state: "present"
|
||||||
|
part_end: "100MiB"
|
||||||
|
flags:
|
||||||
|
- "boot"
|
||||||
|
- name: "Create a partition for /root with room enough for the base installation and some extra packages. Everything else is going to run in a container."
|
||||||
|
parted:
|
||||||
|
device: "{{ disk_device }}"
|
||||||
|
number: 2
|
||||||
|
state: "present"
|
||||||
|
part_start: "100MiB"
|
||||||
|
part_end: "1GiB"
|
||||||
|
- name: "Data holds everything else."
|
||||||
|
parted:
|
||||||
|
device: "{{ disk_device }}"
|
||||||
|
number: 3
|
||||||
|
state: "present"
|
||||||
|
part_start: "1GiB"
|
64
tasks/post_install.yml
Normal file
64
tasks/post_install.yml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
- name: "Set a password for the root user."
|
||||||
|
shell: "PATH=/usr/bin:/usr/sbin:/bin:/sbin chroot /mnt /bin/sh -c 'echo root:{{ root }} | chpasswd -'"
|
||||||
|
- name: "Enable default services."
|
||||||
|
shell: "PATH=/usr/bin:/usr/sbin:/bin:/sbin chroot /mnt rc-update add {{ item.service }} {{ item.runlevel }}"
|
||||||
|
args:
|
||||||
|
creates: "/mnt/etc/runlevels/{{ item.runlevel }}/{{ item.service }}"
|
||||||
|
loop:
|
||||||
|
- runlevel: "sysinit"
|
||||||
|
service: "devfs"
|
||||||
|
- runlevel: "sysinit"
|
||||||
|
service: "dmesg"
|
||||||
|
- runlevel: "sysinit"
|
||||||
|
service: "mdev"
|
||||||
|
- runlevel: "sysinit"
|
||||||
|
service: "hwdrivers"
|
||||||
|
- runlevel: "sysinit"
|
||||||
|
service: "modloop"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "modules"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "sysctl"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "hostname"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "bootmisc"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "syslog"
|
||||||
|
- runlevel: "shutdown"
|
||||||
|
service: "mount-ro"
|
||||||
|
- runlevel: "shutdown"
|
||||||
|
service: "killprocs"
|
||||||
|
- runlevel: "shutdown"
|
||||||
|
service: "savecache"
|
||||||
|
- runlevel: "default"
|
||||||
|
service: "networking"
|
||||||
|
- runlevel: "default"
|
||||||
|
service: "iptables"
|
||||||
|
- runlevel: "default"
|
||||||
|
service: "ip6tables"
|
||||||
|
- runlevel: "default"
|
||||||
|
service: "node-exporter"
|
||||||
|
- runlevel: "default"
|
||||||
|
service: "sshd"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "hwclock"
|
||||||
|
- runlevel: "boot"
|
||||||
|
service: "swclock"
|
||||||
|
- name: "Install firewall rules."
|
||||||
|
template:
|
||||||
|
src: "templates/{{ item }}.j2"
|
||||||
|
dest: "/mnt/etc/iptables/{{ item }}"
|
||||||
|
loop:
|
||||||
|
- rules-save
|
||||||
|
- rules6-save
|
||||||
|
- name: "And blocklists."
|
||||||
|
template:
|
||||||
|
src: "templates/{{ item }}.j2"
|
||||||
|
dest: "/mnt/etc/ipset.d/{{ item }}"
|
||||||
|
loop:
|
||||||
|
- blocklist4
|
||||||
|
- blocklist6
|
||||||
|
- name: "Reboot!"
|
||||||
|
reboot:
|
8
tasks/root.yml
Normal file
8
tasks/root.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
- name: root
|
||||||
|
parted:
|
||||||
|
device: "{{ device }}"
|
||||||
|
number: 2
|
||||||
|
state: present
|
||||||
|
part_start: 100MiB
|
||||||
|
part_end: 1GiB
|
1
templates/blocklist4.j2
Normal file
1
templates/blocklist4.j2
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hash:ip family inet hashsize 1024 maxelem 65536
|
1
templates/blocklist6.j2
Normal file
1
templates/blocklist6.j2
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hash:ip family inet6 hashsize 1024 maxelem 65536
|
4
templates/fstab.j2
Normal file
4
templates/fstab.j2
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{{disk_device}}2 / btrfs compress=zstd,noatime,nodiratime,lazytime,discard,ro 0 1
|
||||||
|
{{disk_device}}1 /boot ext2 noatime,nodiratime,lazytime,ro 0 2
|
||||||
|
/dev/mapper/srv /srv btrfs compress=zstd,noatime,nodiratime,lazytime,discard,noauto 0 2
|
||||||
|
tmpfs /var/log tmpfs defaults 0 0
|
1
templates/hostname.j2
Normal file
1
templates/hostname.j2
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{{ inventory_hostname }}
|
18
templates/interfaces.j2
Normal file
18
templates/interfaces.j2
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
auto lo
|
||||||
|
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
|
||||||
|
iface eth0 inet static
|
||||||
|
address {{ hostvars[inventory_hostname].ansible_default_ipv4.address }}
|
||||||
|
netmask {{ hostvars[inventory_hostname].ansible_default_ipv4.netmask }}
|
||||||
|
gateway {{ hostvars[inventory_hostname].ansible_default_ipv4.gateway }}
|
||||||
|
|
||||||
|
{% if ip6 is defined %}
|
||||||
|
iface eth0 inet6 static
|
||||||
|
address {{ ip6 }}
|
||||||
|
netmask {{ netmask6 }}
|
||||||
|
gateway {{ gateway6 }}
|
||||||
|
pre-up echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_ra
|
||||||
|
{% endif %}
|
1
templates/mkinitfs.conf.j2
Normal file
1
templates/mkinitfs.conf.j2
Normal file
|
@ -0,0 +1 @@
|
||||||
|
features="ata base cdrom ext4 btrfs cryptsetup network keymap kms mmc nvme raid scsi usb virtio"
|
2
templates/repositories.j2
Normal file
2
templates/repositories.j2
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
http://dl-cdn.alpinelinux.org/alpine/v3.15/main
|
||||||
|
http://dl-cdn.alpinelinux.org/alpine/v3.15/community
|
2
templates/resolv.conf.j2
Normal file
2
templates/resolv.conf.j2
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
nameserver 8.8.8.8
|
12
templates/rules-save.j2
Normal file
12
templates/rules-save.j2
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
*filter
|
||||||
|
:INPUT DROP [0:0]
|
||||||
|
:FORWARD ACCEPT [0:0]
|
||||||
|
:OUTPUT ACCEPT [0:0]
|
||||||
|
-A INPUT -m set --match-set blocklist4 src -j REJECT --reject-with icmp-host-unreachable
|
||||||
|
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A INPUT -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
|
||||||
|
-A INPUT -p udp -m conntrack ! --ctstate NEW -j REJECT --reject-with icmp-port-unreachable
|
||||||
|
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack ! --ctstate NEW -j REJECT --reject-with tcp-reset
|
||||||
|
-A INPUT -i lo -j ACCEPT
|
||||||
|
COMMIT
|
13
templates/rules6-save.j2
Normal file
13
templates/rules6-save.j2
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
*filter
|
||||||
|
:INPUT DROP [0:0]
|
||||||
|
:FORWARD ACCEPT [0:0]
|
||||||
|
:OUTPUT ACCEPT [0:0]
|
||||||
|
-A INPUT -m set --match-set blocklist6 src -j REJECT --reject-with icmp6-addr-unreachable
|
||||||
|
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A INPUT -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A INPUT -p icmpv6 -j ACCEPT
|
||||||
|
-A FORWARD -p icmpv6 -j ACCEPT
|
||||||
|
-A INPUT -p udp -m conntrack ! --ctstate NEW -j REJECT --reject-with icmp6-port-unreachable
|
||||||
|
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack ! --ctstate NEW -j REJECT --reject-with tcp-reset
|
||||||
|
-A INPUT -i lo -j ACCEPT
|
||||||
|
COMMIT
|
74
templates/update-extlinux.conf.j2
Normal file
74
templates/update-extlinux.conf.j2
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# configuration for extlinux config builder
|
||||||
|
|
||||||
|
# overwrite
|
||||||
|
# Overwrite current /boot/extlinux.conf. If this is not '1' we will only
|
||||||
|
# write to /boot/extlinux.conf.new
|
||||||
|
overwrite=1
|
||||||
|
|
||||||
|
# vesa_menu
|
||||||
|
# use fancy vesa menu (vesamenu.c32) menus, won't work with serial
|
||||||
|
vesa_menu=0
|
||||||
|
|
||||||
|
# default_kernel_opts
|
||||||
|
# default kernel options
|
||||||
|
default_kernel_opts=quiet
|
||||||
|
|
||||||
|
# modules
|
||||||
|
# modules which should be loaded before pivot_root
|
||||||
|
modules=sd-mod,usb-storage,ext4,btrfs
|
||||||
|
|
||||||
|
# root
|
||||||
|
# root device - if not specified, will be guessed using
|
||||||
|
# blkid -o export /dev/root
|
||||||
|
root={{ disk_device }}2
|
||||||
|
|
||||||
|
# verbose
|
||||||
|
# if set to non-zero, update-extlinux will be a lot more verbose.
|
||||||
|
verbose=0
|
||||||
|
|
||||||
|
# hidden
|
||||||
|
# if set to non-zero, the boot menu will be hidden by default.
|
||||||
|
hidden=1
|
||||||
|
|
||||||
|
# timeout
|
||||||
|
# number of seconds to wait before booting default
|
||||||
|
timeout=3
|
||||||
|
|
||||||
|
# default
|
||||||
|
# default kernel to boot
|
||||||
|
default=virt
|
||||||
|
|
||||||
|
# serial_port
|
||||||
|
# serial port number - if not specified, serial console will be disabled
|
||||||
|
serial_port=
|
||||||
|
|
||||||
|
# serial_baud
|
||||||
|
# the baudrate for the serial port. Will use 115200 if unset
|
||||||
|
serial_baud=115200
|
||||||
|
|
||||||
|
# xen_opts
|
||||||
|
# options to hand to xen hypervisor, useful ones are:
|
||||||
|
# dom0_mem=384M (give domain-0 environment 384M ram)
|
||||||
|
xen_opts=dom0_mem=384M
|
||||||
|
|
||||||
|
# if you copy /usr/share/syslinux/reboot.c32 to /boot/, a menu entry
|
||||||
|
# will be auto-generated for it
|
||||||
|
|
||||||
|
# if you copy hdt.c32, libgpl.c32, and libmenu.c32 from /usr/share/syslinux/
|
||||||
|
# to /boot/, a menu entry will be auto-generated for HDT
|
||||||
|
|
||||||
|
# if you download and install /boot/memtest, then if HDT is present it
|
||||||
|
# will use it, else a separate menu entry will be auto-generated for
|
||||||
|
# memtest
|
||||||
|
|
||||||
|
# optional password
|
||||||
|
# you can generate a SHA512 password using: mkpasswd
|
||||||
|
#
|
||||||
|
# if you assign a password, you should make this file world-unreadable
|
||||||
|
#
|
||||||
|
# if a password is assigned, the menu entries can't be edited at boot
|
||||||
|
# time, and HDT if present is password-protected
|
||||||
|
#
|
||||||
|
# you can also include "MENU PASSWD" in any custom entries you have in
|
||||||
|
# /etc/update-extlinux.d/
|
||||||
|
password=''
|
Loading…
Reference in a new issue