mirror of
https://github.com/philmmanjaro/alma.git
synced 2025-07-27 07:29:28 +02:00
Compare commits
No commits in common. "master" and "v0.7.0" have entirely different histories.
@ -1,3 +0,0 @@
|
|||||||
target
|
|
||||||
.git
|
|
||||||
Dockerfile
|
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Please do not open bug reports if you're using Manjaro.
|
|
||||||
ALMA does not support Manjaro. If it works then have fun, but if it doesn't then I won't fix it -->
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Before requesting a feature, note that ALMA isn't intended to be a generic Arch Linux installer. It's an installer focused on creating mutable live environments. In addition, avoid asking for new features which can be easily implemented using the preset system -->
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
31
.github/workflows/rust.yml
vendored
31
.github/workflows/rust.yml
vendored
@ -1,31 +0,0 @@
|
|||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
name: Check format
|
|
||||||
with:
|
|
||||||
command: fmt
|
|
||||||
args: --all -- --check
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
name: Run clippy
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --all-targets --locked -- -D warnings
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
name: Run tests
|
|
||||||
with:
|
|
||||||
command: test
|
|
787
Cargo.lock
generated
787
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@ -1,20 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "alma"
|
name = "alma"
|
||||||
version = "0.10.0"
|
version = "0.7.0"
|
||||||
authors = ["Roey Darwish Dror"]
|
authors = ["Roey Darwish Dror"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
which = "4"
|
which = "2.0.1"
|
||||||
log = "0.4"
|
failure = "0.1.5"
|
||||||
structopt = "0.3"
|
log = "0.4.6"
|
||||||
tempfile = "3"
|
structopt = "0.2.14"
|
||||||
serde = { version = "1", features = ["derive"] }
|
simplelog = "0.5.3"
|
||||||
toml = "0.5"
|
tempfile = "3.0.5"
|
||||||
byte-unit = "4.0"
|
nix = "0.12.0"
|
||||||
nix = "0.19"
|
serde = { version = "1.0.92", features = ["derive"] }
|
||||||
env_logger = "0.8"
|
toml = "0.5.1"
|
||||||
pretty_env_logger = "0.4"
|
byte-unit = "2.1.0"
|
||||||
dialoguer = "0.7"
|
|
||||||
console = "0.13"
|
|
||||||
anyhow = "1"
|
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,11 +0,0 @@
|
|||||||
FROM rust AS builder
|
|
||||||
ADD . /src
|
|
||||||
WORKDIR /src
|
|
||||||
RUN cargo build --release
|
|
||||||
|
|
||||||
FROM archlinux/base
|
|
||||||
RUN pacman -Sy --needed --noconfirm gptfdisk parted arch-install-scripts dosfstools coreutils util-linux cryptsetup
|
|
||||||
COPY --from=builder /src/target/release/alma /usr/bin/alma
|
|
||||||
|
|
||||||
CMD alma
|
|
||||||
WORKDIR /work
|
|
36
PKGBUILD
36
PKGBUILD
@ -1,36 +0,0 @@
|
|||||||
# Maintainer: James McMurray <jamesmcm03@gmail.com>
|
|
||||||
# Contributor: Roey Darwish Dror <roey.ghost@gmail.com>
|
|
||||||
|
|
||||||
_pkgname="alma"
|
|
||||||
pkgname="alma-git"
|
|
||||||
pkgver=r108.3ca2e01
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc='Create Arch Linux based live USB'
|
|
||||||
arch=('x86_64')
|
|
||||||
url='https://github.com/philmmanjaro/alma'
|
|
||||||
license=('GPL3')
|
|
||||||
makedepends=('git' 'rust')
|
|
||||||
depends=('gptfdisk' 'parted' 'arch-install-scripts' 'dosfstools' 'coreutils' 'util-linux')
|
|
||||||
optdepends=('cryptsetup: for root filesystem encryption')
|
|
||||||
source=("git+https://github.com/philmmanjaro/${_pkgname}")
|
|
||||||
provides=('alma')
|
|
||||||
conflicts=('alma')
|
|
||||||
sha256sums=('SKIP')
|
|
||||||
|
|
||||||
pkgver() {
|
|
||||||
cd "${srcdir}/${_pkgname}"
|
|
||||||
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "${srcdir}/${_pkgname}"
|
|
||||||
|
|
||||||
cargo build --release
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "${srcdir}/${_pkgname}"
|
|
||||||
|
|
||||||
install -Dm755 target/release/${_pkgname} "${pkgdir}/usr/bin/${_pkgname}"
|
|
||||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${_pkgname}/LICENSE"
|
|
||||||
}
|
|
184
README.md
184
README.md
@ -1,29 +1,29 @@
|
|||||||
# ALMA - Arch Linux Mobile Appliance
|
# ALMA - Arch Linux Mobile Appliance
|
||||||
|
|
||||||
Almost every live Linux distribution out there is meant for a specific purpose, whether it's data
|
This tool installs Arch Linux into a USB drive, making it a customized live Arch Linux bootable
|
||||||
rescue, privacy, penetration testing or anything else. There are some more generic distributions
|
drive. It was inspired by [this](http://valleycat.org/linux/arch-usb.html) article. The USB drive
|
||||||
but all of them are based on squashfs, meaning that changes don't persist reboots.
|
should be bootable both by UEFI and legacy boot.
|
||||||
|
|
||||||
ALMA is meant for those who wish to have a **mutable** live environment. It installs Arch
|
|
||||||
Linux into a USB or an SD card, almost as if it was a hard drive. Some configuration is applied in
|
|
||||||
order to minimize writes to the USB and making sure the system is bootable on both BIOS and UEFI
|
|
||||||
systems.
|
|
||||||
|
|
||||||
Upgrading your packages is as easy as running `pacman -Syu` (or [Topgrade](https://github.com/r-darwish/topgrade/)) while the system is
|
|
||||||
booted. This tool also provides an easy chroot command, so you can keep your live environment up to
|
|
||||||
date without having to boot it. Encrypting the root partition is as easy as providing the `-e` flag
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
You can either build the project using cargo build or install the `alma` package from AUR.
|
You can either build the project using cargo build or install the `alma` package from AUR.
|
||||||
|
|
||||||
### Using Arch Linux derivatives
|
## Requirements
|
||||||
|
|
||||||
Using Arch Linux derivatives, is supported with this ALMA fork. You may edit the base.toml as needed.
|
This tool should be ran from an exiting Arch Linux installations. It depends on the following tools:
|
||||||
|
|
||||||
|
* sgdisk
|
||||||
|
* partprobe
|
||||||
|
* Arch install scripts
|
||||||
|
* mkfs.fat
|
||||||
|
* mkfs.ext4
|
||||||
|
* *Optional*: cryptsetup
|
||||||
|
|
||||||
|
Dependencies will be handled for you if you install alma from AUR.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Image creation on removable device
|
### Creation
|
||||||
``` shell
|
``` shell
|
||||||
sudo alma create /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
sudo alma create /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
||||||
```
|
```
|
||||||
@ -31,154 +31,34 @@ sudo alma create /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
|||||||
This will wipe the entire disk and create a bootable installation of Arch Linux. You can use either
|
This will wipe the entire disk and create a bootable installation of Arch Linux. You can use either
|
||||||
removable devices or loop devices. As a precaution, ALMA will not wipe non-removable devices.
|
removable devices or loop devices. As a precaution, ALMA will not wipe non-removable devices.
|
||||||
|
|
||||||
Not specifying any path will cause ALMA to interactively prompt the user for a removable device.
|
|
||||||
|
|
||||||
### Disk encryption
|
|
||||||
|
|
||||||
You can enable disk encryption with the `-e` flag:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
sudo alma create -e /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
|
||||||
```
|
|
||||||
|
|
||||||
You will be prompted to enter and confirm the encryption passphrase during image creation.
|
|
||||||
|
|
||||||
### chroot
|
|
||||||
|
|
||||||
After the installation is done you can either boot from it immediately or use `arch-chroot` to
|
After the installation is done you can either boot from it immediately or use `arch-chroot` to
|
||||||
perform further customizations before your first boot (e.g. installing wireless device drivers).
|
perform further customizations before your first boot.
|
||||||
|
|
||||||
You can run `arch-chroot` via ALMA:
|
### Chrooting to exiting Live USB
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
sudo alma chroot /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
sudo alma chroot /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create raw image and boot in qemu
|
### Flags
|
||||||
|
* `-p / --extra-packages` - Specify extra packages to install. For example: `-p htop tmux`
|
||||||
|
* `-i / --interactive` - Drop you into interactive chroot to perform further customization
|
||||||
|
* `-e / --encrypted-root` - Encrypt the root filesystem.
|
||||||
|
|
||||||
For development and testing it may be useful to generate and boot the image in qemu.
|
## What exactly does it do?
|
||||||
|
|
||||||
Creating a 10GiB raw image, with disk encryption:
|
This tool doesn't aspire to be a generic installer for Arch Linux. Instead, it does the minimum
|
||||||
|
steps required to create a bootable USB with a few tweaks.
|
||||||
|
|
||||||
``` shell
|
1. Partition the disk as suggested [here](http://valleycat.org/linux/arch-usb.html). The last
|
||||||
sudo alma create -e --image 10GiB almatest.img
|
partition will be formatted as ext4
|
||||||
```
|
1. Bootstrap the system using `pacstrap -c`. The `-c` flag will use the host's cache instead the
|
||||||
|
drive's cache, which will speed up things when you create multiple drives. This tool will install
|
||||||
If you receive the following error:
|
the base system, grub, intel-ucode and NetworkManager
|
||||||
```
|
1. Generate initramfs without the `autodetect` hook
|
||||||
Error setting up a loop device: losetup: cannot find an unused loop device
|
1. Set NetworkManager to start at boot
|
||||||
```
|
1. Install GRUB in both legacy and UEFI modes
|
||||||
|
|
||||||
Check that you are running ALMA with sudo privileges, and reboot if you have installed a kernel update since your last reboot.
|
|
||||||
|
|
||||||
Mounting the raw image to a loop device:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
sudo losetup -f ./almatest.img
|
|
||||||
```
|
|
||||||
|
|
||||||
Check loop device:
|
|
||||||
``` shell
|
|
||||||
sudo losetup -j ./almatest.img
|
|
||||||
```
|
|
||||||
```
|
|
||||||
/dev/loop0: [2070]:6865917 (/path/to/image/almatest.img)
|
|
||||||
```
|
|
||||||
Note that your loop device number may differ.
|
|
||||||
|
|
||||||
Run qemu via ALMA:
|
|
||||||
``` shell
|
|
||||||
sudo alma qemu /dev/loop0
|
|
||||||
```
|
|
||||||
|
|
||||||
This will boot the image in qemu.
|
|
||||||
|
|
||||||
## Presets
|
|
||||||
|
|
||||||
Reproducing a build can be easily done using a preset file.
|
|
||||||
|
|
||||||
Preset files are simple TOML files which contain:
|
|
||||||
* A list of packages to install: `packages = ["mypackage"]`
|
|
||||||
* A post-installation script: `script = """ ... """`
|
|
||||||
* Environment variables required by the preset (e.g. used in the script): `enironment_variables = ["USERNAME"]`
|
|
||||||
* A list of shared directories `shared_directories = ["subdirectory"]` - where subdirectory would be available at `/shared_dirs/subdirectory/` for use in the script of the preset.
|
|
||||||
|
|
||||||
See the presets directory for examples.
|
|
||||||
|
|
||||||
Presets are used via the `--presets` argument (multiple preset files or directories may be provided):
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
sudo ALMA_USER=archie alma create /dev/disk/by-id/usb-Generic_USB_Flash_Disk-0:0 --presets ./presets/user.toml ./presets/custom_preset.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Preset scripts are executed in the same order they are provided.
|
|
||||||
|
|
||||||
If a directory is provided, then all files and subdirectories in the directory are recursively crawled in alphanumeric order (all files must be ALMA .toml files). This allows you to use the following structure to compose many scripts in a specific order:
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── 00-add_user.toml
|
|
||||||
├── 01-xorg
|
|
||||||
│ ├── 00-install.toml
|
|
||||||
│ └── 01-config.toml
|
|
||||||
└── 02-i3
|
|
||||||
├── 00-install.toml
|
|
||||||
└── 01-copy_dotfiles.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Example preset TOML:
|
|
||||||
|
|
||||||
``` toml
|
|
||||||
packages = ["sudo"]
|
|
||||||
script = """
|
|
||||||
set -eux
|
|
||||||
useradd -m ${ALMA_USER}
|
|
||||||
passwd ${ALMA_USER}
|
|
||||||
usermod -G wheel -a ${ALMA_USER}
|
|
||||||
echo "%wheel ALL=(ALL) ALL" > /etc/sudoers.d/wheel
|
|
||||||
"""
|
|
||||||
environment_variables = ["ALMA_USER"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that shared directories in the preset scripts are mounted as bind mounts, so they are *not* mounted read-only. Any changes the custom script makes to the shared directory will be carried out in the preset shared directory of the host system, so be sure to copy (not move) files from the shared directories.
|
|
||||||
|
|
||||||
### Order of execution
|
|
||||||
|
|
||||||
ALMA installs the packages and presets in the following order:
|
|
||||||
|
|
||||||
1. All non-AUR packages are installed
|
|
||||||
2. If AUR packages are present in the toml files, yay (or another
|
|
||||||
specified AUR helper) is installed
|
|
||||||
3. All AUR packages are installed.
|
|
||||||
4. Preset scripts are executed according to their filenames in
|
|
||||||
alphanumeric order.
|
|
||||||
|
|
||||||
Note this may mean you have to workaround some package installations if
|
|
||||||
they depend on preset scripts.
|
|
||||||
|
|
||||||
For example, at the moment you cannot install Rust-based AUR packages in
|
|
||||||
the `aur_packages` array of the Preset TOMLs if you use rustup,
|
|
||||||
since rustup needs to be given the toolchain to
|
|
||||||
install first. This can be worked around by carrying out the AUR
|
|
||||||
package installation inside the preset script itself in these cases.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
### mkinitcpio: /etc/mkinitcpio.d/linux.preset: No such file or directory
|
|
||||||
|
|
||||||
Ensure you have both the `linux` and `base` packages installed. Note
|
|
||||||
that only Arch Linux is supported, not Arch Linux derivatives such as
|
|
||||||
Manjaro.
|
|
||||||
|
|
||||||
### Problem opening /dev/... for reading! Error is 123.
|
|
||||||
|
|
||||||
Delete all partitions on the disk first (e.g. with gparted) and try
|
|
||||||
again.
|
|
||||||
|
|
||||||
## Similar projects
|
## Similar projects
|
||||||
|
|
||||||
* [NomadBSD](http://nomadbsd.org/)
|
* [NomadBSD](http://nomadbsd.org/)
|
||||||
|
|
||||||
## Useful Resources
|
|
||||||
|
|
||||||
* [Arch Wiki: Installing Arch Linux on a USB key](https://wiki.archlinux.org/index.php/Install_Arch_Linux_on_a_USB_key)
|
|
||||||
* [ValleyCat's Arch Linux USB guide](http://valleycat.org/linux/arch-usb.html?i=1)
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
packages = ["alsa-firmware", "alsa-utils", "pavucontrol", "pulseaudio-bluetooth", "pulseaudio-ctl", "pulseaudio-zeroconf"]
|
|
@ -1,2 +0,0 @@
|
|||||||
packages = ["clang"]
|
|
||||||
aur_packages = ["bat-cat-git"]
|
|
@ -1 +0,0 @@
|
|||||||
packages = ["linux61", "linux61-broadcom-wl", "linux-firmware", "grub", "efibootmgr", "intel-ucode", "amd-ucode", "networkmanager"]
|
|
@ -1,5 +0,0 @@
|
|||||||
script = """
|
|
||||||
ls /shared_dirs/copy_file_example/
|
|
||||||
cp /shared_dirs/copy_file_example/testfile.txt /root/
|
|
||||||
"""
|
|
||||||
shared_directories = ["copy_file_example"]
|
|
@ -1 +0,0 @@
|
|||||||
test
|
|
1
presets/installer.toml
Normal file
1
presets/installer.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
packages = ['arch-install-scripts']
|
@ -1,11 +1,6 @@
|
|||||||
packages = ["plasma-desktop", "plasma-nm", "plasma-pa", "dolphin", "gwenview", "konsole", "ttf-dejavu", "sddm", "kate", "xdg-desktop-portal-kde", "discover", "falkon"]
|
packages = ["plasma-desktop", "dolphin", "gwenview", "konsole", "ttf-dejavu", "sddm"]
|
||||||
script = """
|
script = """
|
||||||
set -exu
|
set -exu
|
||||||
|
|
||||||
systemctl enable sddm
|
systemctl enable sddm
|
||||||
|
|
||||||
# Set theme
|
|
||||||
mkdir -p /etc/sddm.conf.d
|
|
||||||
echo "[Theme]" > /etc/sddm.conf.d/00_theme_settings.conf
|
|
||||||
echo "Current=breeze" >> /etc/sddm.conf.d/00_theme_settings.conf
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
packages = ["pamac-gtk", "pamac-snap-plugin", "pamac-flatpak-plugin"]
|
|
||||||
script = """
|
|
||||||
set -exu
|
|
||||||
|
|
||||||
systemctl enable apparmor
|
|
||||||
systemctl enable snapd.apparmor
|
|
||||||
systemctl enable snapd
|
|
||||||
"""
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
../base.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xorg.toml
|
|
@ -1 +0,0 @@
|
|||||||
../user.toml
|
|
@ -1 +0,0 @@
|
|||||||
../kde.toml
|
|
@ -1 +0,0 @@
|
|||||||
../steam.toml
|
|
@ -1 +0,0 @@
|
|||||||
../audio.toml
|
|
@ -1 +0,0 @@
|
|||||||
../base.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xorg.toml
|
|
@ -1 +0,0 @@
|
|||||||
../user.toml
|
|
@ -1 +0,0 @@
|
|||||||
../kde.toml
|
|
@ -1 +0,0 @@
|
|||||||
../audio.toml
|
|
@ -1,13 +0,0 @@
|
|||||||
packages = ["steam", "gamescope-session-git", "gamescope-plus", "wget", "vulkan-icd-loader", "lib32-vulkan-icd-loader", "vulkan-intel", "lib32-vulkan-intel", "vulkan-radeon", "lib32-vulkan-radeon", "ttf-liberation", "adobe-source-sans-pro-fonts", "adobe-source-han-sans-jp-fonts", "adobe-source-han-sans-kr-fonts", "adobe-source-han-sans-cn-fonts"]
|
|
||||||
script = """
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
wget -v https://gitlab.com/evlaV/jupiter_steam-jupiter-stable-PKGBUILD/-/raw/5cd60f3cd66527a95f93e6fefd9371fd659a5aea/steam_jupiter_stable_bootstrapped_20230316.1.tar.xz -O /usr/lib/steam/bootstraplinux_ubuntu12_32.tar.xz
|
|
||||||
|
|
||||||
mkdir -p /etc/sddm.conf.d
|
|
||||||
echo "# Created by Manjaro ALMA" > /etc/sddm.conf.d/99-autologin.conf
|
|
||||||
echo "[Autologin]" >> /etc/sddm.conf.d/99-autologin.conf
|
|
||||||
echo "User=${ALMA_USER}" >> /etc/sddm.conf.d/99-autologin.conf
|
|
||||||
echo "Session=gamescope-session.desktop" >> /etc/sddm.conf.d/99-autologin.conf
|
|
||||||
"""
|
|
||||||
environment_variables = ["ALMA_USER"]
|
|
@ -2,8 +2,8 @@ packages = ["sudo"]
|
|||||||
script = """
|
script = """
|
||||||
set -eux
|
set -eux
|
||||||
|
|
||||||
useradd -m ${ALMA_USER} -p $(openssl passwd -6 ${ALMA_USER_PASSWORD})
|
useradd -m ${ALMA_USER}
|
||||||
usermod -G users,lp,video,network,storage,wheel,audio -a ${ALMA_USER}
|
passwd ${ALMA_USER}
|
||||||
echo "%wheel ALL=(ALL) ALL" > /etc/sudoers.d/wheel
|
usermod -G wheel -a ${ALMA_USER}
|
||||||
"""
|
"""
|
||||||
environment_variables = ["ALMA_USER", "ALMA_USER_PASSWORD"]
|
environment_variables = ["ALMA_USER"]
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
packages = ["manjaro-xfce-settings", "manjaro-hello", "manjaro-application-utility", "manjaro-settings-manager-notifier", "manjaro-documentation-en", "manjaro-browser-settings", "manjaro-release", "manjaro-firmware", "manjaro-system"]
|
|
||||||
script = """
|
|
||||||
cp /shared_dirs/xfce-branding/lightdm-gtk-greeter.conf /etc/lightdm/lightdm-gtk-greeter.conf
|
|
||||||
"""
|
|
||||||
shared_directories = ["xfce-branding"]
|
|
@ -1,16 +0,0 @@
|
|||||||
[greeter]
|
|
||||||
background = /usr/share/backgrounds/illyria-default-lockscreen.jpg
|
|
||||||
user-background = false
|
|
||||||
font-name = Cantarell Bold 12
|
|
||||||
xft-antialias = true
|
|
||||||
icon-theme-name = Adapta-Papirus-Maia
|
|
||||||
screensaver-timeout = 60
|
|
||||||
theme-name = Matcha-sea
|
|
||||||
cursor-theme-name = xcursor-breeze
|
|
||||||
show-clock = false
|
|
||||||
default-user-image = #manjaro
|
|
||||||
xft-hintstyle = hintfull
|
|
||||||
position = 50%,center 57%,center
|
|
||||||
clock-format =
|
|
||||||
panel-position = bottom
|
|
||||||
indicators = ~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power
|
|
@ -1,2 +0,0 @@
|
|||||||
packages = ["xfce4-goodies", "xfce4-pulseaudio-plugin", "pulseaudio", "pavucontrol", "mugshot", "engrampa", "catfish", "firefox", "screenfetch", "thunderbird", "network-manager-applet"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
../base.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xorg.toml
|
|
@ -1 +0,0 @@
|
|||||||
../user.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xfce.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xfce-goodies.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xfce-branding.toml
|
|
@ -1 +0,0 @@
|
|||||||
../pamac.toml
|
|
@ -1 +0,0 @@
|
|||||||
../audio.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xfce-branding
|
|
@ -1,6 +0,0 @@
|
|||||||
packages = ["xfce4", "ttf-dejavu", "lightdm-gtk-greeter-settings", "accountsservice"]
|
|
||||||
script = """
|
|
||||||
set -exu
|
|
||||||
|
|
||||||
systemctl enable lightdm
|
|
||||||
"""
|
|
@ -1 +0,0 @@
|
|||||||
../base.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xorg.toml
|
|
@ -1 +0,0 @@
|
|||||||
../user.toml
|
|
@ -1 +0,0 @@
|
|||||||
../xfce.toml
|
|
@ -1 +0,0 @@
|
|||||||
../audio.toml
|
|
@ -1 +0,0 @@
|
|||||||
packages = ["xf86-input-libinput", "xf86-video-amdgpu", "xf86-video-ati", "xf86-video-nouveau", "xorg-server", "xterm"]
|
|
13
presets/zfs.toml
Normal file
13
presets/zfs.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
script = """
|
||||||
|
set -exu
|
||||||
|
|
||||||
|
cat << 'EOF' >> /etc/pacman.conf
|
||||||
|
[archzfs]
|
||||||
|
Server = https://archzfs.com/$repo/$arch
|
||||||
|
EOF
|
||||||
|
|
||||||
|
pacman-key -r F75D9D76
|
||||||
|
pacman-key --lsign-key F75D9D76
|
||||||
|
|
||||||
|
pacman -Sy archzfs-linux
|
||||||
|
"""
|
48
src/args.rs
48
src/args.rs
@ -1,12 +1,9 @@
|
|||||||
use super::aur::AurHelper;
|
|
||||||
use byte_unit::Byte;
|
use byte_unit::Byte;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// Parse size argument as bytes
|
|
||||||
/// e.g. 10GB, 10GiB, etc.
|
|
||||||
fn parse_bytes(src: &str) -> Result<Byte, &'static str> {
|
fn parse_bytes(src: &str) -> Result<Byte, &'static str> {
|
||||||
Byte::from_str(src).map_err(|_| "Invalid image size")
|
Byte::from_string(src).map_err(|_| "Invalid image size")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
@ -36,26 +33,12 @@ pub enum Command {
|
|||||||
pub struct CreateCommand {
|
pub struct CreateCommand {
|
||||||
/// Either a path to a removable block device or a nonexiting file if --image is specified
|
/// Either a path to a removable block device or a nonexiting file if --image is specified
|
||||||
#[structopt(parse(from_os_str))]
|
#[structopt(parse(from_os_str))]
|
||||||
pub path: Option<PathBuf>,
|
pub path: PathBuf,
|
||||||
|
|
||||||
/// Path to a pacman.conf file which will be used to pacstrap packages into the image.
|
/// Additional pacakges to install
|
||||||
///
|
|
||||||
/// This pacman.conf will also be copied into the resulting Arch Linux image.
|
|
||||||
#[structopt(short = "c", long = "pacman-conf", value_name = "pacman_conf")]
|
|
||||||
pub pacman_conf: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Additional packages to install
|
|
||||||
#[structopt(short = "p", long = "extra-packages", value_name = "package")]
|
#[structopt(short = "p", long = "extra-packages", value_name = "package")]
|
||||||
pub extra_packages: Vec<String>,
|
pub extra_packages: Vec<String>,
|
||||||
|
|
||||||
/// Additional packages to install
|
|
||||||
#[structopt(long = "aur-packages", value_name = "aurpackage")]
|
|
||||||
pub aur_packages: Vec<String>,
|
|
||||||
|
|
||||||
/// Boot partition size in megabytes
|
|
||||||
#[structopt(long = "boot-size")]
|
|
||||||
pub boot_size: Option<u32>,
|
|
||||||
|
|
||||||
/// Enter interactive chroot before unmounting the drive
|
/// Enter interactive chroot before unmounting the drive
|
||||||
#[structopt(short = "i", long = "interactive")]
|
#[structopt(short = "i", long = "interactive")]
|
||||||
pub interactive: bool,
|
pub interactive: bool,
|
||||||
@ -71,25 +54,10 @@ pub struct CreateCommand {
|
|||||||
/// Create an image with a certain size in the given path instead of using an actual block device
|
/// Create an image with a certain size in the given path instead of using an actual block device
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long = "image",
|
long = "image",
|
||||||
parse(try_from_str = parse_bytes),
|
parse(try_from_str = "parse_bytes"),
|
||||||
value_name = "size",
|
value_name = "size"
|
||||||
requires = "path"
|
|
||||||
)]
|
)]
|
||||||
pub image: Option<Byte>,
|
pub image: Option<Byte>,
|
||||||
|
|
||||||
/// Overwrite existing image files. Use with caution!
|
|
||||||
#[structopt(long = "overwrite")]
|
|
||||||
pub overwrite: bool,
|
|
||||||
|
|
||||||
/// Allow installation on non-removable devices. Use with extreme caution!
|
|
||||||
///
|
|
||||||
/// If no device is specified in the command line, the device selection menu will
|
|
||||||
/// show non-removable devices
|
|
||||||
#[structopt(long = "allow-non-removable")]
|
|
||||||
pub allow_non_removable: bool,
|
|
||||||
|
|
||||||
#[structopt(long = "aur-helper", possible_values=&["paru", "yay"], default_value="paru")]
|
|
||||||
pub aur_helper: AurHelper,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
@ -98,9 +66,9 @@ pub struct ChrootCommand {
|
|||||||
#[structopt(parse(from_os_str))]
|
#[structopt(parse(from_os_str))]
|
||||||
pub block_device: PathBuf,
|
pub block_device: PathBuf,
|
||||||
|
|
||||||
/// Allow installation on non-removable devices. Use with extreme caution!
|
/// Open an encrypted root partition
|
||||||
#[structopt(long = "allow-non-removable")]
|
#[structopt(short = "e", long = "encrypted-root")]
|
||||||
pub allow_non_removable: bool,
|
pub encrypted_root: bool,
|
||||||
|
|
||||||
/// Optional command to run
|
/// Optional command to run
|
||||||
#[structopt()]
|
#[structopt()]
|
||||||
|
60
src/aur.rs
60
src/aur.rs
@ -1,60 +0,0 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub struct AurHelper {
|
|
||||||
pub name: String,
|
|
||||||
pub package_name: String,
|
|
||||||
pub install_command: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for AurHelper {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
match s {
|
|
||||||
"paru" => Ok(Self {
|
|
||||||
name: String::from("paru"),
|
|
||||||
package_name: String::from("paru-bin"),
|
|
||||||
install_command: vec![
|
|
||||||
String::from("paru"),
|
|
||||||
String::from("-S"),
|
|
||||||
String::from("--skipreview"),
|
|
||||||
String::from("--noupgrademenu"),
|
|
||||||
String::from("--useask"),
|
|
||||||
String::from("--removemake"),
|
|
||||||
String::from("--norebuild"),
|
|
||||||
String::from("--nocleanafter"),
|
|
||||||
String::from("--noredownload"),
|
|
||||||
String::from("--mflags"),
|
|
||||||
String::from(""),
|
|
||||||
String::from("--noconfirm"),
|
|
||||||
String::from("--batchinstall"),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
"yay" => Ok(Self {
|
|
||||||
name: String::from("yay"),
|
|
||||||
package_name: String::from("yay-bin"),
|
|
||||||
install_command: vec![
|
|
||||||
String::from("yay"),
|
|
||||||
String::from("-S"),
|
|
||||||
String::from("--nocleanmenu"),
|
|
||||||
String::from("--nodiffmenu"),
|
|
||||||
String::from("--noeditmenu"),
|
|
||||||
String::from("--noupgrademenu"),
|
|
||||||
String::from("--useask"),
|
|
||||||
String::from("--removemake"),
|
|
||||||
String::from("--norebuild"),
|
|
||||||
String::from("--answerdiff"),
|
|
||||||
String::from("None"),
|
|
||||||
String::from("--answeredit"),
|
|
||||||
String::from("None"),
|
|
||||||
String::from("--answerclean"),
|
|
||||||
String::from("None"),
|
|
||||||
String::from("--mflags"),
|
|
||||||
String::from("--noconfirm"),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
_ => Err(anyhow!("Error parsing AUR helper string: {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
pub const BOOT_PARTITION_INDEX: u8 = 1;
|
|
||||||
pub const ROOT_PARTITION_INDEX: u8 = 3;
|
|
||||||
|
|
||||||
pub static JOURNALD_CONF: &str = "
|
|
||||||
[Journal]
|
|
||||||
Storage=volatile
|
|
||||||
SystemMaxUse=16M
|
|
||||||
";
|
|
||||||
|
|
||||||
pub const BASE_PACKAGES: [&str; 1] = ["base"];
|
|
||||||
|
|
||||||
pub const AUR_DEPENDENCIES: [&str; 3] = ["base-devel", "git", "sudo"];
|
|
121
src/error.rs
Normal file
121
src/error.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use failure::{Backtrace, Context, Fail};
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
inner: Context<ErrorKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[fail(display = "Error quering information about the block device")]
|
||||||
|
DeviceQuery,
|
||||||
|
|
||||||
|
#[fail(display = "Invalid device name")]
|
||||||
|
InvalidDeviceName,
|
||||||
|
|
||||||
|
#[fail(display = "The given block device is neither removable nor a loop device")]
|
||||||
|
DangerousDevice,
|
||||||
|
|
||||||
|
#[fail(display = "Partition {} does not exist", _0)]
|
||||||
|
NoSuchPartition(u8),
|
||||||
|
|
||||||
|
#[fail(display = "Could not find {}", _0)]
|
||||||
|
NoTool(&'static str),
|
||||||
|
|
||||||
|
#[fail(display = "Error creating a temporary directory")]
|
||||||
|
TmpDirError,
|
||||||
|
|
||||||
|
#[fail(display = "Partitioning error")]
|
||||||
|
Partitioning,
|
||||||
|
|
||||||
|
#[fail(display = "Error formatting filesystems")]
|
||||||
|
Formatting,
|
||||||
|
|
||||||
|
#[fail(display = "Error mounting filesystems")]
|
||||||
|
Mounting,
|
||||||
|
|
||||||
|
#[fail(display = "Error creating the boot directory")]
|
||||||
|
CreateBoot,
|
||||||
|
|
||||||
|
#[fail(display = "Pacstrap error")]
|
||||||
|
Pacstrap,
|
||||||
|
|
||||||
|
#[fail(display = "fstab error")]
|
||||||
|
Fstab,
|
||||||
|
|
||||||
|
#[fail(display = "Post installation configuration error")]
|
||||||
|
PostInstallation,
|
||||||
|
|
||||||
|
#[fail(display = "Initramfs error")]
|
||||||
|
Initramfs,
|
||||||
|
|
||||||
|
#[fail(display = "Bootloader error")]
|
||||||
|
Bootloader,
|
||||||
|
|
||||||
|
#[fail(display = "Error caused by the interactive mode")]
|
||||||
|
Interactive,
|
||||||
|
|
||||||
|
#[fail(display = "Failed umounting filesystems")]
|
||||||
|
UmountFailure,
|
||||||
|
|
||||||
|
#[fail(display = "Error setting up an encrypted device")]
|
||||||
|
LuksSetup,
|
||||||
|
|
||||||
|
#[fail(display = "Error opening the encrypted device")]
|
||||||
|
LuksOpen,
|
||||||
|
|
||||||
|
#[fail(display = "Error closing the encrypted device")]
|
||||||
|
LuksClose,
|
||||||
|
|
||||||
|
#[fail(display = "Error setting the locale")]
|
||||||
|
Locale,
|
||||||
|
|
||||||
|
#[fail(display = "Failed launching Qemu")]
|
||||||
|
Qemu,
|
||||||
|
|
||||||
|
#[fail(display = "Error loading preset \"{}\"", _0)]
|
||||||
|
Preset(String),
|
||||||
|
|
||||||
|
#[fail(display = "Missing environment variables \"{:?}\"", _0)]
|
||||||
|
MissingEnvironmentVariables(Vec<String>),
|
||||||
|
|
||||||
|
#[fail(display = "Error executing preset script")]
|
||||||
|
PresetScript,
|
||||||
|
|
||||||
|
#[fail(display = "Error creating the image")]
|
||||||
|
Image,
|
||||||
|
|
||||||
|
#[fail(display = "Error setting up a loop device: {}", _0)]
|
||||||
|
Losetup(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fail for Error {
|
||||||
|
fn cause(&self) -> Option<&Fail> {
|
||||||
|
self.inner.cause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backtrace(&self) -> Option<&Backtrace> {
|
||||||
|
self.inner.backtrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&self.inner, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorKind> for Error {
|
||||||
|
fn from(kind: ErrorKind) -> Error {
|
||||||
|
Error {
|
||||||
|
inner: Context::new(kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Context<ErrorKind>> for Error {
|
||||||
|
fn from(inner: Context<ErrorKind>) -> Error {
|
||||||
|
Error { inner }
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
pub struct Initcpio {
|
|
||||||
encrypted: bool,
|
|
||||||
plymouth: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Initcpio {
|
|
||||||
pub fn new(encrypted: bool, plymouth: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
encrypted,
|
|
||||||
plymouth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_config(&self) -> anyhow::Result<String> {
|
|
||||||
let mut output = String::from(
|
|
||||||
"MODULES=()
|
|
||||||
BINARIES=()
|
|
||||||
FILES=()
|
|
||||||
HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block ",
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.encrypted {
|
|
||||||
output.write_str("encrypt ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.plymouth {
|
|
||||||
output.write_str("filesystems plymouth)\n")?;
|
|
||||||
} else {
|
|
||||||
output.write_str("filesystems fsck)\n")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
}
|
|
563
src/main.rs
563
src/main.rs
@ -1,62 +1,73 @@
|
|||||||
mod args;
|
mod args;
|
||||||
mod aur;
|
mod error;
|
||||||
mod constants;
|
|
||||||
mod initcpio;
|
|
||||||
mod presets;
|
mod presets;
|
||||||
mod process;
|
mod process;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod tool;
|
mod tool;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use crate::args::*;
|
||||||
use args::Command;
|
use crate::error::*;
|
||||||
|
use crate::process::CommandExt;
|
||||||
|
use crate::storage::*;
|
||||||
|
use crate::tool::Tool;
|
||||||
use byte_unit::Byte;
|
use byte_unit::Byte;
|
||||||
use console::style;
|
use failure::{Fail, ResultExt};
|
||||||
use dialoguer::{theme::ColorfulTheme, Select};
|
use log::{debug, error, info, warn};
|
||||||
use log::{debug, error, info, log_enabled, Level, LevelFilter};
|
use nix::sys::signal;
|
||||||
use process::CommandExt;
|
use simplelog::*;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::{fs::PermissionsExt, process::CommandExt as UnixCommandExt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::process::Command as ProcessCommand;
|
use std::process::exit;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use storage::EncryptedDevice;
|
|
||||||
use storage::{BlockDevice, Filesystem, FilesystemType, LoopDevice, MountStack};
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use tool::Tool;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
const BOOT_PARTITION_INDEX: u8 = 1;
|
||||||
// Get struct of args using structopt
|
const ROOT_PARTITION_INDEX: u8 = 3;
|
||||||
let app = args::App::from_args();
|
|
||||||
|
|
||||||
// Set up logging
|
static MKINITCPIO: &'static str = "MODULES=()
|
||||||
let mut builder = pretty_env_logger::formatted_timed_builder();
|
BINARIES=()
|
||||||
let log_level = if app.verbose {
|
FILES=()
|
||||||
LevelFilter::Debug
|
HOOKS=(base udev keyboard consolefont block encrypt filesystems keyboard fsck)";
|
||||||
} else {
|
|
||||||
LevelFilter::Info
|
|
||||||
};
|
|
||||||
builder.filter_level(log_level);
|
|
||||||
builder.init();
|
|
||||||
|
|
||||||
// Match command from arguments and run relevant code
|
static JOURNALD_CONF: &'static str = "
|
||||||
match app.cmd {
|
[Journal]
|
||||||
Command::Create(command) => create(command),
|
Storage=volatile
|
||||||
Command::Chroot(command) => tool::chroot(command),
|
SystemMaxUse=16M
|
||||||
Command::Qemu(command) => tool::qemu(command),
|
";
|
||||||
}?;
|
|
||||||
|
|
||||||
Ok(())
|
fn mount<'a>(
|
||||||
|
mount_path: &Path,
|
||||||
|
boot_filesystem: &'a Filesystem,
|
||||||
|
root_filesystem: &'a Filesystem,
|
||||||
|
) -> Result<MountStack<'a>, Error> {
|
||||||
|
let mut mount_stack = MountStack::new();
|
||||||
|
debug!(
|
||||||
|
"Root partition: {}",
|
||||||
|
root_filesystem.block().path().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Mounting filesystems to {}", mount_path.display());
|
||||||
|
mount_stack
|
||||||
|
.mount(&root_filesystem, mount_path.into(), None)
|
||||||
|
.context(ErrorKind::Mounting)?;
|
||||||
|
|
||||||
|
let boot_point = mount_path.join("boot");
|
||||||
|
if !boot_point.exists() {
|
||||||
|
fs::create_dir(&boot_point).context(ErrorKind::CreateBoot)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_stack
|
||||||
|
.mount(&boot_filesystem, boot_point, None)
|
||||||
|
.context(ErrorKind::Mounting)?;
|
||||||
|
|
||||||
|
Ok(mount_stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove swap entry from fstab and any commented lines
|
|
||||||
/// Returns an owned String
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `fstab` - A string slice holding the contents of the fstab file
|
|
||||||
fn fix_fstab(fstab: &str) -> String {
|
fn fix_fstab(fstab: &str) -> String {
|
||||||
fstab
|
fstab
|
||||||
.lines()
|
.lines()
|
||||||
@ -65,56 +76,23 @@ fn fix_fstab(fstab: &str) -> String {
|
|||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a file at the path provided, and mounts it to a loop device
|
fn create_image(path: &Path, size: Byte) -> Result<LoopDevice, Error> {
|
||||||
fn create_image(path: &Path, size: Byte, overwrite: bool) -> anyhow::Result<LoopDevice> {
|
|
||||||
{
|
{
|
||||||
let mut options = fs::OpenOptions::new();
|
let file = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
options.write(true);
|
.create_new(true)
|
||||||
if overwrite {
|
.open(path)
|
||||||
options.create(true);
|
.context(ErrorKind::Image)?;
|
||||||
} else {
|
|
||||||
options.create_new(true);
|
|
||||||
}
|
|
||||||
let file = options.open(path).context("Error creating the image")?;
|
|
||||||
|
|
||||||
file.set_len(size.get_bytes() as u64)
|
file.set_len(size.get_bytes() as u64)
|
||||||
.context("Error creating the image")?;
|
.context(ErrorKind::Image)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoopDevice::create(path)
|
LoopDevice::create(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests selection of block device (no device was given in the arguments)
|
fn create(command: CreateCommand) -> Result<(), Error> {
|
||||||
fn select_block_device(allow_non_removable: bool) -> anyhow::Result<PathBuf> {
|
let presets = presets::Presets::load(&command.presets)?;
|
||||||
let devices = storage::get_storage_devices(allow_non_removable)?;
|
|
||||||
|
|
||||||
if devices.is_empty() {
|
|
||||||
return Err(anyhow!("There are no removable devices"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if allow_non_removable {
|
|
||||||
println!(
|
|
||||||
"{}\n",
|
|
||||||
style("Showing non-removable devices. Make sure you select the correct device.")
|
|
||||||
.red()
|
|
||||||
.bold()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let selection = Select::with_theme(&ColorfulTheme::default())
|
|
||||||
.with_prompt("Select a removable device")
|
|
||||||
.default(0)
|
|
||||||
.items(&devices)
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
Ok(PathBuf::from("/dev").join(&devices[selection].name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the installation
|
|
||||||
#[allow(clippy::cognitive_complexity)] // TODO: Split steps into functions and remove this
|
|
||||||
fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|
||||||
let presets = presets::PresetsCollection::load(&command.presets)?;
|
|
||||||
|
|
||||||
let sgdisk = Tool::find("sgdisk")?;
|
let sgdisk = Tool::find("sgdisk")?;
|
||||||
let pacstrap = Tool::find("pacstrap")?;
|
let pacstrap = Tool::find("pacstrap")?;
|
||||||
@ -133,14 +111,8 @@ fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let storage_device_path = if let Some(path) = command.path {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
select_block_device(command.allow_non_removable)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let image_loop = if let Some(size) = command.image {
|
let image_loop = if let Some(size) = command.image {
|
||||||
Some(create_image(&storage_device_path, size, command.overwrite)?)
|
Some(create_image(&command.path, size)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -152,43 +124,38 @@ fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|||||||
info!("Using loop device at {}", loop_dev.path().display());
|
info!("Using loop device at {}", loop_dev.path().display());
|
||||||
loop_dev.path()
|
loop_dev.path()
|
||||||
})
|
})
|
||||||
.unwrap_or(&storage_device_path),
|
.unwrap_or(&command.path),
|
||||||
command.allow_non_removable,
|
|
||||||
)?;
|
)?;
|
||||||
|
let mount_point = tempdir().context(ErrorKind::TmpDirError)?;
|
||||||
let mount_point = tempdir().context("Error creating a temporary directory")?;
|
|
||||||
let disk_path = storage_device.path();
|
let disk_path = storage_device.path();
|
||||||
|
|
||||||
info!("Partitioning the block device");
|
info!("Partitioning the block device");
|
||||||
debug!("{:?}", disk_path);
|
debug!("{:?}", disk_path);
|
||||||
|
|
||||||
let boot_size = command.boot_size.unwrap_or(300);
|
|
||||||
|
|
||||||
sgdisk
|
sgdisk
|
||||||
.execute()
|
.execute()
|
||||||
.args([
|
.args(&[
|
||||||
"-Z",
|
"-Z",
|
||||||
"-o",
|
"-o",
|
||||||
&format!("--new=1::+{}M", boot_size),
|
"--new=1::+100M",
|
||||||
"--new=2::+1M",
|
"--new=2::+1M",
|
||||||
"--largest-new=3",
|
"--largest-new=3",
|
||||||
"--typecode=1:EF00",
|
"--typecode=1:EF00",
|
||||||
"--typecode=2:EF02",
|
"--typecode=2:EF02",
|
||||||
])
|
])
|
||||||
.arg(disk_path)
|
.arg(&disk_path)
|
||||||
.run()
|
.run(ErrorKind::Partitioning)?;
|
||||||
.context("Partitioning error")?;
|
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(1000));
|
thread::sleep(Duration::from_millis(1000));
|
||||||
|
|
||||||
info!("Formatting filesystems");
|
info!("Formatting filesystems");
|
||||||
let boot_partition = storage_device.get_partition(constants::BOOT_PARTITION_INDEX)?;
|
let boot_partition = storage_device.get_partition(BOOT_PARTITION_INDEX)?;
|
||||||
let boot_filesystem = Filesystem::format(&boot_partition, FilesystemType::Vfat, &mkfat)?;
|
let boot_filesystem = Filesystem::format(&boot_partition, FilesystemType::Vfat, &mkfat)?;
|
||||||
|
|
||||||
let root_partition_base = storage_device.get_partition(constants::ROOT_PARTITION_INDEX)?;
|
let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?;
|
||||||
let encrypted_root = if let Some(cryptsetup) = &cryptsetup {
|
let encrypted_root = if let Some(cryptsetup) = &cryptsetup {
|
||||||
info!("Encrypting the root filesystem");
|
info!("Encrypting the root filesystem");
|
||||||
EncryptedDevice::prepare(cryptsetup, &root_partition_base)?;
|
EncryptedDevice::prepare(&cryptsetup, &root_partition_base)?;
|
||||||
Some(EncryptedDevice::open(
|
Some(EncryptedDevice::open(
|
||||||
cryptsetup,
|
cryptsetup,
|
||||||
&root_partition_base,
|
&root_partition_base,
|
||||||
@ -199,213 +166,71 @@ fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let root_partition = if let Some(e) = encrypted_root.as_ref() {
|
let root_partition = if let Some(e) = encrypted_root.as_ref() {
|
||||||
e as &dyn BlockDevice
|
e as &BlockDevice
|
||||||
} else {
|
} else {
|
||||||
&root_partition_base as &dyn BlockDevice
|
&root_partition_base as &BlockDevice
|
||||||
};
|
};
|
||||||
|
|
||||||
let root_filesystem = Filesystem::format(root_partition, FilesystemType::Ext4, &mkext4)?;
|
let root_filesystem = Filesystem::format(root_partition, FilesystemType::Ext4, &mkext4)?;
|
||||||
|
|
||||||
let mount_stack = tool::mount(mount_point.path(), &boot_filesystem, &root_filesystem)?;
|
let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?;
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
let mut packages: HashSet<String> = [
|
||||||
debug!("lsblk:");
|
"base",
|
||||||
ProcessCommand::new("lsblk")
|
"grub",
|
||||||
.arg("--fs")
|
"efibootmgr",
|
||||||
.spawn()
|
"intel-ucode",
|
||||||
.and_then(|mut p| p.wait())
|
"networkmanager",
|
||||||
.map_err(|e| {
|
"broadcom-wl",
|
||||||
error!("Error running lsblk: {}", e);
|
]
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut packages: HashSet<String> = constants::BASE_PACKAGES
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| String::from(*s))
|
.map(|s| String::from(*s))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
packages.extend(presets.packages);
|
packages.extend(presets.packages);
|
||||||
|
|
||||||
let aur_packages = {
|
|
||||||
let mut p = vec![String::from("shim-signed")];
|
|
||||||
p.extend(presets.aur_packages);
|
|
||||||
p.extend(command.aur_packages);
|
|
||||||
p
|
|
||||||
};
|
|
||||||
|
|
||||||
packages.extend(constants::AUR_DEPENDENCIES.iter().map(|s| String::from(*s)));
|
|
||||||
|
|
||||||
let pacman_conf_path = command
|
|
||||||
.pacman_conf
|
|
||||||
.unwrap_or_else(|| "/etc/pacman.conf".into());
|
|
||||||
|
|
||||||
info!("Bootstrapping system");
|
info!("Bootstrapping system");
|
||||||
pacstrap
|
pacstrap
|
||||||
.execute()
|
.execute()
|
||||||
.arg("-C")
|
|
||||||
.arg(&pacman_conf_path)
|
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.args(packages)
|
.args(packages)
|
||||||
.args(&command.extra_packages)
|
.args(&command.extra_packages)
|
||||||
.run()
|
.run(ErrorKind::Pacstrap)?;
|
||||||
.context("Pacstrap error")?;
|
|
||||||
|
|
||||||
// Copy pacman.conf to the image.
|
|
||||||
fs::copy(pacman_conf_path, mount_point.path().join("etc/pacman.conf"))
|
|
||||||
.context("Failed copying pacman.conf")?;
|
|
||||||
|
|
||||||
let fstab = fix_fstab(
|
let fstab = fix_fstab(
|
||||||
&genfstab
|
&genfstab
|
||||||
.execute()
|
.execute()
|
||||||
.arg("-U")
|
.arg("-U")
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.run_text_output()
|
.run_text_output(ErrorKind::Fstab)?,
|
||||||
.context("fstab error")?,
|
|
||||||
);
|
);
|
||||||
debug!("fstab:\n{}", fstab);
|
debug!("fstab:\n{}", fstab);
|
||||||
fs::write(mount_point.path().join("etc/fstab"), fstab).context("fstab error")?;
|
fs::write(mount_point.path().join("etc/fstab"), fstab).context(ErrorKind::Fstab)?;
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(["passwd", "-d", "root"])
|
|
||||||
.run()
|
|
||||||
.context("Failed to delete the root password")?;
|
|
||||||
|
|
||||||
info!("Setting locale");
|
|
||||||
fs::OpenOptions::new()
|
|
||||||
.append(true)
|
|
||||||
.write(true)
|
|
||||||
.open(mount_point.path().join("etc/locale.gen"))
|
|
||||||
.and_then(|mut locale_gen| locale_gen.write_all(b"en_US.UTF-8 UTF-8\n"))
|
|
||||||
.context("Failed to create locale.gen")?;
|
|
||||||
fs::write(
|
|
||||||
mount_point.path().join("etc/locale.conf"),
|
|
||||||
"LANG=en_US.UTF-8",
|
|
||||||
)
|
|
||||||
.context("Failed to write to locale.conf")?;
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.arg("locale-gen")
|
|
||||||
.run()
|
|
||||||
.context("locale-gen failed")?;
|
|
||||||
|
|
||||||
info!("Installing AUR packages");
|
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(["useradd", "-m", "aur"])
|
|
||||||
.run()
|
|
||||||
.context("Failed to create temporary user to install AUR packages")?;
|
|
||||||
|
|
||||||
let aur_sudoers = mount_point.path().join("etc/sudoers.d/aur");
|
|
||||||
fs::write(&aur_sudoers, "aur ALL=(ALL) NOPASSWD: ALL")
|
|
||||||
.context("Failed to modify sudoers file for AUR packages")?;
|
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(["sudo", "-u", "aur"])
|
|
||||||
.arg("git")
|
|
||||||
.arg("clone")
|
|
||||||
.arg(format!(
|
|
||||||
"https://aur.archlinux.org/{}.git",
|
|
||||||
&command.aur_helper.package_name
|
|
||||||
))
|
|
||||||
.arg(format!("/home/aur/{}", &command.aur_helper.name))
|
|
||||||
.run()
|
|
||||||
.context("Failed to clone AUR helper package")?;
|
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args([
|
|
||||||
"bash",
|
|
||||||
"-c",
|
|
||||||
&format!(
|
|
||||||
"cd /home/aur/{} && sudo -u aur makepkg -s -i --noconfirm",
|
|
||||||
&command.aur_helper.name
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.run()
|
|
||||||
.context("Failed to build AUR helper")?;
|
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(["sudo", "-u", "aur"])
|
|
||||||
.args(command.aur_helper.install_command)
|
|
||||||
.args(aur_packages)
|
|
||||||
.run()
|
|
||||||
.context("Failed to install AUR packages")?;
|
|
||||||
|
|
||||||
// Clean up aur user:
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(["userdel", "-r", "aur"])
|
|
||||||
.run()
|
|
||||||
.context("Failed to delete temporary aur user")?;
|
|
||||||
|
|
||||||
fs::remove_file(&aur_sudoers).context("Cannot delete the AUR sudoers temporary file")?;
|
|
||||||
|
|
||||||
if !presets.scripts.is_empty() {
|
if !presets.scripts.is_empty() {
|
||||||
info!("Running custom scripts");
|
info!("Running custom scripts");
|
||||||
}
|
}
|
||||||
|
|
||||||
for script in presets.scripts {
|
for script in presets.scripts {
|
||||||
let mut bind_mount_stack = MountStack::new();
|
let mut script_file =
|
||||||
if let Some(shared_dirs) = &script.shared_dirs {
|
tempfile::NamedTempFile::new_in(mount_point.path()).context(ErrorKind::PresetScript)?;
|
||||||
for dir in shared_dirs {
|
|
||||||
// Create shared directories mount points inside chroot
|
|
||||||
std::fs::create_dir_all(
|
|
||||||
mount_point
|
|
||||||
.path()
|
|
||||||
.join(PathBuf::from("shared_dirs/"))
|
|
||||||
.join(dir.file_name().expect("Dir had no filename")),
|
|
||||||
)
|
|
||||||
.context("Failed mounting shared directories in preset")?;
|
|
||||||
|
|
||||||
// Bind mount shared directories
|
|
||||||
let target = mount_point
|
|
||||||
.path()
|
|
||||||
.join(PathBuf::from("shared_dirs/"))
|
|
||||||
.join(dir.file_name().expect("Dir had no filename"));
|
|
||||||
bind_mount_stack
|
|
||||||
.bind_mount(dir.clone(), target, None)
|
|
||||||
.context("Failed mounting shared directories in preset")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut script_file = tempfile::NamedTempFile::new_in(mount_point.path())
|
|
||||||
.context("Failed creating temporary preset script")?;
|
|
||||||
script_file
|
script_file
|
||||||
.write_all(script.script_text.as_bytes())
|
.write_all(script.as_bytes())
|
||||||
.and_then(|_| script_file.as_file_mut().metadata())
|
.and_then(|_| script_file.as_file_mut().metadata())
|
||||||
.and_then(|metadata| {
|
.and_then(|metadata| {
|
||||||
let mut permissions = metadata.permissions();
|
let mut permissions = metadata.permissions();
|
||||||
permissions.set_mode(0o755);
|
permissions.set_mode(0o755);
|
||||||
fs::set_permissions(script_file.path(), permissions)
|
fs::set_permissions(script_file.path(), permissions)
|
||||||
})
|
})
|
||||||
.context("Failed creating temporary preset script")?;
|
.context(ErrorKind::PresetScript)?;
|
||||||
|
|
||||||
let script_path = script_file.into_temp_path();
|
let script_path = script_file.into_temp_path();
|
||||||
arch_chroot
|
arch_chroot
|
||||||
.execute()
|
.execute()
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.arg(
|
.arg(Path::new("/").join(script_path.file_name().unwrap()))
|
||||||
Path::new("/").join(
|
.run(ErrorKind::PostInstallation)?;
|
||||||
script_path
|
|
||||||
.file_name()
|
|
||||||
.expect("Script path had no file name"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.run()
|
|
||||||
.with_context(|| format!("Failed running preset script:\n{}", script.script_text))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Performing post installation tasks");
|
info!("Performing post installation tasks");
|
||||||
@ -413,95 +238,82 @@ fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|||||||
arch_chroot
|
arch_chroot
|
||||||
.execute()
|
.execute()
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.args(["systemctl", "enable", "NetworkManager"])
|
.args(&["systemctl", "enable", "NetworkManager"])
|
||||||
.run()
|
.run(ErrorKind::PostInstallation)?;
|
||||||
.context("Failed to enable NetworkManager")?;
|
|
||||||
|
|
||||||
info!("Configuring journald");
|
info!("Configuring journald");
|
||||||
fs::write(
|
fs::write(
|
||||||
mount_point.path().join("etc/systemd/journald.conf"),
|
mount_point.path().join("etc/systemd/journald.conf"),
|
||||||
constants::JOURNALD_CONF,
|
JOURNALD_CONF,
|
||||||
)
|
)
|
||||||
.context("Failed to write to journald.conf")?;
|
.context(ErrorKind::PostInstallation)?;
|
||||||
|
|
||||||
info!("Generating initramfs");
|
info!("Setting locale");
|
||||||
let plymouth_exists = Path::new(&mount_point.path().join("usr/bin/plymouth")).exists();
|
fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.write(true)
|
||||||
|
.open(mount_point.path().join("etc/locale.gen"))
|
||||||
|
.and_then(|mut locale_gen| locale_gen.write_all(b"en_US.UTF-8 UTF-8\n"))
|
||||||
|
.context(ErrorKind::Locale)?;
|
||||||
fs::write(
|
fs::write(
|
||||||
mount_point.path().join("etc/mkinitcpio.conf"),
|
mount_point.path().join("etc/locale.conf"),
|
||||||
initcpio::Initcpio::new(encrypted_root.is_some(), plymouth_exists).to_config()?,
|
"LANG=en_US.UTF-8",
|
||||||
)
|
)
|
||||||
.context("Failed to write to mkinitcpio.conf")?;
|
.context(ErrorKind::Locale)?;
|
||||||
arch_chroot
|
arch_chroot
|
||||||
.execute()
|
.execute()
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.args(["mkinitcpio", "-P"])
|
.arg("locale-gen")
|
||||||
.run()
|
.run(ErrorKind::Locale)?;
|
||||||
.context("Failed to run mkinitcpio - do you have the base and linux packages installed?")?;
|
|
||||||
|
info!("Generating initramfs");
|
||||||
|
fs::write(mount_point.path().join("etc/mkinitcpio.conf"), MKINITCPIO)
|
||||||
|
.context(ErrorKind::Initramfs)?;
|
||||||
|
arch_chroot
|
||||||
|
.execute()
|
||||||
|
.arg(mount_point.path())
|
||||||
|
.args(&["mkinitcpio", "-p", "linux"])
|
||||||
|
.run(ErrorKind::Initramfs)?;
|
||||||
|
|
||||||
if encrypted_root.is_some() {
|
if encrypted_root.is_some() {
|
||||||
debug!("Setting up GRUB for an encrypted root partition");
|
debug!("Setting up GRUB for an encrypted root partition");
|
||||||
|
|
||||||
let uuid = blkid
|
let uuid = blkid
|
||||||
.expect("No tool for blkid")
|
.unwrap()
|
||||||
.execute()
|
.execute()
|
||||||
.arg(root_partition_base.path())
|
.arg(root_partition_base.path())
|
||||||
.args(["-o", "value", "-s", "UUID"])
|
.args(&["-o", "value", "-s", "UUID"])
|
||||||
.run_text_output()
|
.run_text_output(ErrorKind::Partitioning)?;
|
||||||
.context("Failed to run blkid")?;
|
|
||||||
let trimmed = uuid.trim();
|
let trimmed = uuid.trim();
|
||||||
debug!("Root partition UUID: {}", trimmed);
|
debug!("Root partition UUID: {}", trimmed);
|
||||||
|
|
||||||
let mut grub_file = fs::OpenOptions::new()
|
let mut grub_file = fs::OpenOptions::new()
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(mount_point.path().join("etc/default/grub"))
|
.open(mount_point.path().join("etc/default/grub"))
|
||||||
.context("Failed to create /etc/default/grub")?;
|
.context(ErrorKind::Bootloader)?;
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
&mut grub_file,
|
&mut grub_file,
|
||||||
"GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={}:luks_root\"",
|
"GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={}:luks_root\"",
|
||||||
trimmed
|
trimmed
|
||||||
)
|
)
|
||||||
.context("Failed to write to /etc/default/grub")?;
|
.context(ErrorKind::Bootloader)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Installing the Bootloader");
|
info!("Installing the Bootloader");
|
||||||
arch_chroot
|
arch_chroot
|
||||||
.execute()
|
.execute()
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.args(["bash", "-c"])
|
.args(&["bash", "-c"])
|
||||||
.arg(format!("grub-install --target=i386-pc --boot-directory /boot {} && grub-install --target=x86_64-efi --efi-directory /boot --boot-directory /boot --removable && grub-mkconfig -o /boot/grub/grub.cfg", disk_path.display()))
|
.arg(format!("grub-install --target=i386-pc --boot-directory /boot {} && grub-install --target=x86_64-efi --efi-directory /boot --boot-directory /boot --removable && grub-mkconfig -o /boot/grub/grub.cfg", disk_path.display()))
|
||||||
.run().context("Failed to install grub")?;
|
.run(ErrorKind::Bootloader)?;
|
||||||
|
|
||||||
let bootloader = mount_point.path().join("boot/EFI/BOOT/BOOTX64.efi");
|
|
||||||
fs::rename(
|
|
||||||
&bootloader,
|
|
||||||
mount_point.path().join("boot/EFI/BOOT/grubx64.efi"),
|
|
||||||
)
|
|
||||||
.context("Cannot move out grub")?;
|
|
||||||
fs::copy(
|
|
||||||
mount_point.path().join("usr/share/shim-signed/mmx64.efi"),
|
|
||||||
mount_point.path().join("boot/EFI/BOOT/mmx64.efi"),
|
|
||||||
)
|
|
||||||
.context("Failed copying mmx64")?;
|
|
||||||
fs::copy(
|
|
||||||
mount_point.path().join("usr/share/shim-signed/shimx64.efi"),
|
|
||||||
bootloader,
|
|
||||||
)
|
|
||||||
.context("Failed copying shim")?;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"GRUB configuration: {}",
|
|
||||||
fs::read_to_string(mount_point.path().join("boot/grub/grub.cfg"))
|
|
||||||
.unwrap_or_else(|e| e.to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
if command.interactive {
|
if command.interactive {
|
||||||
info!("Dropping you to chroot. Do as you wish to customize the installation. Please exit by typing 'exit' instead of using Ctrl+D");
|
info!("Dropping you to chroot. Do as you wish to customize the installation");
|
||||||
arch_chroot
|
arch_chroot
|
||||||
.execute()
|
.execute()
|
||||||
.arg(mount_point.path())
|
.arg(mount_point.path())
|
||||||
.run()
|
.run(ErrorKind::Interactive)?;
|
||||||
.context("Failed to enter interactive chroot")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Unmounting filesystems");
|
info!("Unmounting filesystems");
|
||||||
@ -509,3 +321,132 @@ fn create(command: args::CreateCommand) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chroot(command: ChrootCommand) -> Result<(), Error> {
|
||||||
|
let arch_chroot = Tool::find("arch-chroot")?;
|
||||||
|
let cryptsetup = if command.encrypted_root {
|
||||||
|
Some(Tool::find("cryptsetup")?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut loop_device: Option<LoopDevice>;
|
||||||
|
let storage_device = match storage::StorageDevice::from_path(&command.block_device) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(_) => {
|
||||||
|
loop_device = Some(LoopDevice::create(&command.block_device)?);
|
||||||
|
storage::StorageDevice::from_path(loop_device.as_ref().unwrap().path())?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mount_point = tempdir().context(ErrorKind::TmpDirError)?;
|
||||||
|
|
||||||
|
let boot_partition = storage_device.get_partition(BOOT_PARTITION_INDEX)?;
|
||||||
|
let boot_filesystem = Filesystem::from_partition(&boot_partition, FilesystemType::Vfat);
|
||||||
|
|
||||||
|
let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?;
|
||||||
|
let encrypted_root = if let Some(cryptsetup) = &cryptsetup {
|
||||||
|
Some(EncryptedDevice::open(
|
||||||
|
cryptsetup,
|
||||||
|
&root_partition_base,
|
||||||
|
"alma_root".into(),
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_partition = if let Some(e) = encrypted_root.as_ref() {
|
||||||
|
e as &BlockDevice
|
||||||
|
} else {
|
||||||
|
&root_partition_base as &BlockDevice
|
||||||
|
};
|
||||||
|
let root_filesystem = Filesystem::from_partition(root_partition, FilesystemType::Ext4);
|
||||||
|
|
||||||
|
let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?;
|
||||||
|
|
||||||
|
arch_chroot
|
||||||
|
.execute()
|
||||||
|
.arg(mount_point.path())
|
||||||
|
.args(&command.command)
|
||||||
|
.run(ErrorKind::Interactive)?;
|
||||||
|
|
||||||
|
info!("Unmounting filesystems");
|
||||||
|
mount_stack.umount()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qemu(command: QemuCommand) -> Result<(), Error> {
|
||||||
|
let qemu = Tool::find("qemu-system-x86_64")?;
|
||||||
|
|
||||||
|
let err = qemu
|
||||||
|
.execute()
|
||||||
|
.args(&[
|
||||||
|
"-enable-kvm",
|
||||||
|
"-cpu",
|
||||||
|
"host",
|
||||||
|
"-m",
|
||||||
|
"4G",
|
||||||
|
"-netdev",
|
||||||
|
"user,id=user.0",
|
||||||
|
"-device",
|
||||||
|
"virtio-net-pci,netdev=user.0",
|
||||||
|
"-device",
|
||||||
|
"qemu-xhci,id=xhci",
|
||||||
|
"-device",
|
||||||
|
"usb-tablet,bus=xhci.0",
|
||||||
|
"-drive",
|
||||||
|
])
|
||||||
|
.arg(format!(
|
||||||
|
"file={},if=virtio,format=raw",
|
||||||
|
command.block_device.display()
|
||||||
|
))
|
||||||
|
.args(command.args)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
Err(err).context(ErrorKind::Qemu)?
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn handle_sigint(_: i32) {
|
||||||
|
warn!("Interrupted");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::from_args();
|
||||||
|
|
||||||
|
let log_level = if app.verbose {
|
||||||
|
LevelFilter::Debug
|
||||||
|
} else {
|
||||||
|
LevelFilter::Info
|
||||||
|
};
|
||||||
|
CombinedLogger::init(vec![TermLogger::new(log_level, Config::default()).unwrap()]).unwrap();
|
||||||
|
|
||||||
|
let sig_action = signal::SigAction::new(
|
||||||
|
signal::SigHandler::Handler(handle_sigint),
|
||||||
|
signal::SaFlags::empty(),
|
||||||
|
signal::SigSet::empty(),
|
||||||
|
);
|
||||||
|
unsafe {
|
||||||
|
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
|
||||||
|
signal::sigaction(signal::SIGTERM, &sig_action).unwrap();
|
||||||
|
signal::sigaction(signal::SIGQUIT, &sig_action).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = match app.cmd {
|
||||||
|
Command::Create(command) => create(command),
|
||||||
|
Command::Chroot(command) => chroot(command),
|
||||||
|
Command::Qemu(command) => qemu(command),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
error!("{}", error);
|
||||||
|
if let Some(cause) = error.cause() {
|
||||||
|
error!("Caused by: {}", cause);
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
149
src/presets.rs
149
src/presets.rs
@ -1,158 +1,67 @@
|
|||||||
use anyhow::{anyhow, Context};
|
use crate::error::{Error, ErrorKind};
|
||||||
|
use failure::ResultExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use toml;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
||||||
struct Preset {
|
struct Preset {
|
||||||
packages: Option<Vec<String>>,
|
packages: Option<Vec<String>>,
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
environment_variables: Option<Vec<String>>,
|
environment_variables: Option<Vec<String>>,
|
||||||
shared_directories: Option<Vec<PathBuf>>,
|
|
||||||
aur_packages: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_dirs(dir: &Path, filevec: &mut Vec<PathBuf>) -> Result<(), io::Error> {
|
|
||||||
if dir.is_dir() {
|
|
||||||
for entry in fs::read_dir(dir)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
visit_dirs(&path, filevec)?;
|
|
||||||
} else if entry.path().extension() == Some(&std::ffi::OsString::from("toml")) {
|
|
||||||
filevec.push(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preset {
|
impl Preset {
|
||||||
fn load(path: &Path) -> anyhow::Result<Self> {
|
fn load(path: &Path) -> Result<Self, Error> {
|
||||||
let data = fs::read_to_string(path).with_context(|| format!("{}", path.display()))?;
|
let data = fs::read_to_string(path)
|
||||||
toml::from_str(&data).with_context(|| format!("{}", path.display()))
|
.with_context(|_| ErrorKind::Preset(format!("{}", path.display())))?;
|
||||||
}
|
Ok(toml::from_str(&data)
|
||||||
|
.with_context(|_| ErrorKind::Preset(format!("{}", path.display())))?)
|
||||||
fn process(
|
|
||||||
&self,
|
|
||||||
packages: &mut HashSet<String>,
|
|
||||||
scripts: &mut Vec<Script>,
|
|
||||||
environment_variables: &mut HashSet<String>,
|
|
||||||
path: &Path,
|
|
||||||
aur_packages: &mut HashSet<String>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if let Some(preset_packages) = &self.packages {
|
|
||||||
packages.extend(preset_packages.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(preset_aur_packages) = &self.aur_packages {
|
|
||||||
aur_packages.extend(preset_aur_packages.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(preset_environment_variables) = &self.environment_variables {
|
|
||||||
environment_variables.extend(preset_environment_variables.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(script_text) = &self.script {
|
|
||||||
scripts.push(Script {
|
|
||||||
script_text: script_text.clone(),
|
|
||||||
shared_dirs: self
|
|
||||||
.shared_directories
|
|
||||||
.clone()
|
|
||||||
.map(|x| {
|
|
||||||
// Convert directories to absolute paths
|
|
||||||
// If any shared directory is not a directory then throw an error
|
|
||||||
x.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|y| {
|
|
||||||
let full_path = path.parent().expect("Path has no parent").join(&y);
|
|
||||||
if full_path.is_dir() {
|
|
||||||
Ok(full_path)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!(
|
|
||||||
"Preset: {} - shared directory: {} is not directory",
|
|
||||||
path.display(),
|
|
||||||
y.display()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<anyhow::Result<Vec<_>>>()
|
|
||||||
})
|
|
||||||
.map_or(Ok(None), |r| r.map(Some))?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Script {
|
pub struct Presets {
|
||||||
pub script_text: String,
|
|
||||||
pub shared_dirs: Option<Vec<PathBuf>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PresetsCollection {
|
|
||||||
pub packages: HashSet<String>,
|
pub packages: HashSet<String>,
|
||||||
pub aur_packages: HashSet<String>,
|
pub scripts: Vec<String>,
|
||||||
pub scripts: Vec<Script>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PresetsCollection {
|
impl Presets {
|
||||||
pub fn load(list: &[PathBuf]) -> anyhow::Result<Self> {
|
pub fn load(list: &[PathBuf]) -> Result<Self, Error> {
|
||||||
let mut packages = HashSet::new();
|
let mut packages = HashSet::new();
|
||||||
let mut aur_packages = HashSet::new();
|
let mut scripts = Vec::new();
|
||||||
let mut scripts: Vec<Script> = Vec::new();
|
|
||||||
let mut environment_variables = HashSet::new();
|
let mut environment_variables = HashSet::new();
|
||||||
|
|
||||||
for preset in list {
|
for preset in list {
|
||||||
if preset.is_dir() {
|
let Preset {
|
||||||
// Build vector of paths to files, then sort by path name
|
script,
|
||||||
// Recursively load directories of preset files
|
packages: preset_packages,
|
||||||
let mut dir_paths: Vec<PathBuf> = Vec::new();
|
environment_variables: preset_environment_variables,
|
||||||
visit_dirs(preset, &mut dir_paths)
|
} = Preset::load(&preset)?;
|
||||||
.with_context(|| format!("{}", preset.display()))?;
|
|
||||||
|
|
||||||
// Order not guaranteed so we sort
|
if let Some(preset_packages) = preset_packages {
|
||||||
// In the future may want to support numerical sort i.e. 15_... < 100_...
|
packages.extend(preset_packages);
|
||||||
dir_paths.sort();
|
}
|
||||||
|
|
||||||
for path in dir_paths {
|
if let Some(preset_environment_variables) = preset_environment_variables {
|
||||||
Preset::load(&path)?.process(
|
environment_variables.extend(preset_environment_variables);
|
||||||
&mut packages,
|
|
||||||
&mut scripts,
|
|
||||||
&mut environment_variables,
|
|
||||||
&path,
|
|
||||||
&mut aur_packages,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Preset::load(preset)?.process(
|
|
||||||
&mut packages,
|
|
||||||
&mut scripts,
|
|
||||||
&mut environment_variables,
|
|
||||||
preset,
|
|
||||||
&mut aur_packages,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scripts.extend(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
let missing_envrionments: Vec<String> = environment_variables
|
let missing_envrionments: Vec<String> = environment_variables
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|var| env::var(var).is_err())
|
.filter(|var| env::var(var).is_err())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !missing_envrionments.is_empty() {
|
if !missing_envrionments.is_empty() {
|
||||||
return Err(anyhow!(
|
Err(ErrorKind::MissingEnvironmentVariables(missing_envrionments))?
|
||||||
"Missing environment variables {:?}",
|
|
||||||
missing_envrionments
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { packages, scripts })
|
||||||
packages,
|
|
||||||
aur_packages,
|
|
||||||
scripts,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,51 @@
|
|||||||
use anyhow::anyhow;
|
use super::error::*;
|
||||||
|
use failure::{Fail, ResultExt};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::process::Command;
|
use std::process::{Command, ExitStatus};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
enum ProcessError {
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
BadExitCode(ExitStatus),
|
||||||
|
|
||||||
|
#[fail(display = "Process output isn't valid UTF-8")]
|
||||||
|
InvalidUtf8,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait CommandExt {
|
pub trait CommandExt {
|
||||||
fn run(&mut self) -> anyhow::Result<()>;
|
fn run(&mut self, context: ErrorKind) -> Result<(), Error>;
|
||||||
fn run_text_output(&mut self) -> anyhow::Result<String>;
|
fn run_text_output(&mut self, context: ErrorKind) -> Result<String, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExt for Command {
|
impl CommandExt for Command {
|
||||||
fn run(&mut self) -> anyhow::Result<()> {
|
fn run(&mut self, context: ErrorKind) -> Result<(), Error> {
|
||||||
let exit_status = self.spawn()?.wait()?;
|
let exit_status = self
|
||||||
|
.spawn()
|
||||||
|
.with_context(|_| context.clone())?
|
||||||
|
.wait()
|
||||||
|
.with_context(|_| context.clone())?;
|
||||||
|
|
||||||
if !exit_status.success() {
|
if !exit_status.success() {
|
||||||
return Err(anyhow!("Bad exit code: {}", exit_status));
|
Err(ProcessError::BadExitCode(exit_status)).with_context(|_| context.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_text_output(&mut self) -> anyhow::Result<String> {
|
fn run_text_output(&mut self, context: ErrorKind) -> Result<String, Error> {
|
||||||
let output = self.output()?;
|
let output = self.output().with_context(|_| context.clone())?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let error = str::from_utf8(&output.stderr).unwrap_or("[INVALID UTF8]");
|
let error = str::from_utf8(&output.stderr).unwrap_or("[INVALID UTF8]");
|
||||||
error!("{}", error);
|
error!("{}", error);
|
||||||
return Err(anyhow!("Bad exit code: {}", output.status));
|
Err(ProcessError::BadExitCode(output.status)).with_context(|_| context.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from(str::from_utf8(&output.stdout).map_err(
|
Ok(String::from(
|
||||||
|_| anyhow!("Process output is not valid UTF-8"),
|
str::from_utf8(&output.stdout)
|
||||||
)?))
|
.map_err(|_| ProcessError::InvalidUtf8)
|
||||||
|
.with_context(|_| context.clone())?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,37 @@
|
|||||||
use super::markers::BlockDevice;
|
use super::markers::BlockDevice;
|
||||||
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::process::CommandExt;
|
use crate::process::CommandExt;
|
||||||
use crate::tool::Tool;
|
use crate::tool::Tool;
|
||||||
use anyhow::Context;
|
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use std::fs;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
static LUKS_MAGIC_1: &[u8] = &[0x4c, 0x55, 0x4b, 0x53, 0xba, 0xbe];
|
|
||||||
static LUKS_MAGIC_2: &[u8] = &[0x53, 0x4b, 0x55, 0x4c, 0xba, 0xbe];
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EncryptedDevice<'t, 'o> {
|
pub struct EncryptedDevice<'t, 'o> {
|
||||||
cryptsetup: &'t Tool,
|
cryptsetup: &'t Tool,
|
||||||
name: String,
|
name: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
origin: PhantomData<&'o dyn BlockDevice>,
|
origin: PhantomData<&'o BlockDevice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t, 'o> EncryptedDevice<'t, 'o> {
|
impl<'t, 'o> EncryptedDevice<'t, 'o> {
|
||||||
pub fn prepare(cryptsetup: &Tool, device: &dyn BlockDevice) -> anyhow::Result<()> {
|
pub fn prepare(cryptsetup: &Tool, device: &BlockDevice) -> Result<(), Error> {
|
||||||
debug!("Preparing encrypted device in {}", device.path().display());
|
debug!("Preparing encrypted device in {}", device.path().display());
|
||||||
cryptsetup
|
cryptsetup
|
||||||
.execute()
|
.execute()
|
||||||
.arg("luksFormat")
|
.arg("luksFormat")
|
||||||
.arg("-q")
|
.arg("-q")
|
||||||
.arg(device.path())
|
.arg(device.path())
|
||||||
.run()
|
.run(ErrorKind::LuksSetup)?;
|
||||||
.context("Error setting up an encrypted device")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(
|
pub fn open(
|
||||||
cryptsetup: &'t Tool,
|
cryptsetup: &'t Tool,
|
||||||
device: &'o dyn BlockDevice,
|
device: &'o BlockDevice,
|
||||||
name: String,
|
name: String,
|
||||||
) -> anyhow::Result<EncryptedDevice<'t, 'o>> {
|
) -> Result<EncryptedDevice<'t, 'o>, Error> {
|
||||||
debug!(
|
debug!(
|
||||||
"Opening encrypted device {} as {}",
|
"Opening encrypted device {} as {}",
|
||||||
device.path().display(),
|
device.path().display(),
|
||||||
@ -48,8 +42,7 @@ impl<'t, 'o> EncryptedDevice<'t, 'o> {
|
|||||||
.arg("open")
|
.arg("open")
|
||||||
.arg(device.path())
|
.arg(device.path())
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.run()
|
.run(ErrorKind::LuksOpen)?;
|
||||||
.context("Error opening the encrypted device")?;
|
|
||||||
|
|
||||||
let path = PathBuf::from("/dev/mapper").join(&name);
|
let path = PathBuf::from("/dev/mapper").join(&name);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -60,14 +53,13 @@ impl<'t, 'o> EncryptedDevice<'t, 'o> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _close(&mut self) -> anyhow::Result<()> {
|
fn _close(&mut self) -> Result<(), Error> {
|
||||||
debug!("Closing encrypted device {}", self.name);
|
debug!("Closing encrypted device {}", self.name);
|
||||||
self.cryptsetup
|
self.cryptsetup
|
||||||
.execute()
|
.execute()
|
||||||
.arg("close")
|
.arg("close")
|
||||||
.arg(&self.name)
|
.arg(&self.name)
|
||||||
.run()
|
.run(ErrorKind::LuksClose)?;
|
||||||
.context("Error closing the encrypted device")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -86,17 +78,3 @@ impl<'t, 'o> BlockDevice for EncryptedDevice<'t, 'o> {
|
|||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_encrypted_device(device: &dyn BlockDevice) -> anyhow::Result<bool> {
|
|
||||||
let mut f = fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(false)
|
|
||||||
.open(device.path())
|
|
||||||
.context("Error detecting whether the root partition is an encrypted device")?;
|
|
||||||
|
|
||||||
let mut buffer = [0; 6];
|
|
||||||
f.read_exact(&mut buffer)
|
|
||||||
.context("Error detecting whether the root partition is an encrypted device")?;
|
|
||||||
|
|
||||||
Ok(buffer == LUKS_MAGIC_1 || buffer == LUKS_MAGIC_2)
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use super::markers::BlockDevice;
|
use super::markers::BlockDevice;
|
||||||
use crate::{process::CommandExt, tool::Tool};
|
use crate::{
|
||||||
use anyhow::Context;
|
error::{Error, ErrorKind},
|
||||||
|
process::CommandExt,
|
||||||
|
tool::Tool,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum FilesystemType {
|
pub enum FilesystemType {
|
||||||
@ -20,31 +23,31 @@ impl FilesystemType {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Filesystem<'a> {
|
pub struct Filesystem<'a> {
|
||||||
fs_type: FilesystemType,
|
fs_type: FilesystemType,
|
||||||
block: &'a dyn BlockDevice,
|
block: &'a BlockDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Filesystem<'a> {
|
impl<'a> Filesystem<'a> {
|
||||||
pub fn format(
|
pub fn format(
|
||||||
block: &'a dyn BlockDevice,
|
block: &'a BlockDevice,
|
||||||
fs_type: FilesystemType,
|
fs_type: FilesystemType,
|
||||||
mkfs: &Tool,
|
mkfs: &Tool,
|
||||||
) -> anyhow::Result<Self> {
|
) -> Result<Self, Error> {
|
||||||
let mut command = mkfs.execute();
|
let mut command = mkfs.execute();
|
||||||
match fs_type {
|
match fs_type {
|
||||||
FilesystemType::Ext4 => command.arg("-F").arg(block.path()),
|
FilesystemType::Ext4 => command.arg("-F").arg(block.path()),
|
||||||
FilesystemType::Vfat => command.arg("-F32").arg(block.path()),
|
FilesystemType::Vfat => command.arg("-F32").arg(block.path()),
|
||||||
};
|
};
|
||||||
|
|
||||||
command.run().context("Error formatting filesystem")?;
|
command.run(ErrorKind::Formatting)?;
|
||||||
|
|
||||||
Ok(Self { fs_type, block })
|
Ok(Self { fs_type, block })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_partition(block: &'a dyn BlockDevice, fs_type: FilesystemType) -> Self {
|
pub fn from_partition(block: &'a BlockDevice, fs_type: FilesystemType) -> Self {
|
||||||
Self { fs_type, block }
|
Self { fs_type, block }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block(&self) -> &dyn BlockDevice {
|
pub fn block(&self) -> &BlockDevice {
|
||||||
self.block
|
self.block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::tool::Tool;
|
use crate::tool::Tool;
|
||||||
use anyhow::{anyhow, Context};
|
use failure::ResultExt;
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -10,27 +11,25 @@ pub struct LoopDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LoopDevice {
|
impl LoopDevice {
|
||||||
pub fn create(file: &Path) -> anyhow::Result<Self> {
|
pub fn create(file: &Path) -> Result<Self, Error> {
|
||||||
let losetup = Tool::find("losetup")?;
|
let losetup = Tool::find("losetup")?;
|
||||||
let output = losetup
|
let output = losetup
|
||||||
.execute()
|
.execute()
|
||||||
.args(["--find", "-P", "--show"])
|
.args(&["--find", "-P", "--show"])
|
||||||
.arg(file)
|
.arg(file)
|
||||||
.output()
|
.output()
|
||||||
.context("Error creating the image")?;
|
.context(ErrorKind::Image)?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(anyhow!(String::from_utf8(output.stderr)?));
|
Err(ErrorKind::Losetup(
|
||||||
|
String::from_utf8(output.stderr).unwrap(),
|
||||||
|
))?
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = PathBuf::from(
|
let path = PathBuf::from(String::from_utf8(output.stdout).unwrap().trim());
|
||||||
String::from_utf8(output.stdout)
|
|
||||||
.context("Output not valid UTF-8")?
|
|
||||||
.trim(),
|
|
||||||
);
|
|
||||||
info!("Mounted {} to {}", file.display(), path.display());
|
info!("Mounted {} to {}", file.display(), path.display());
|
||||||
|
|
||||||
Ok(Self { path, losetup })
|
Ok(LoopDevice { path, losetup })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
pub fn path(&self) -> &Path {
|
||||||
@ -46,7 +45,7 @@ impl Drop for LoopDevice {
|
|||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg(&self.path)
|
.arg(&self.path)
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to spawn command to detach loop device")
|
.unwrap()
|
||||||
.wait()
|
.wait()
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
// Marker traits
|
|
||||||
pub trait BlockDevice: std::fmt::Debug {
|
pub trait BlockDevice: std::fmt::Debug {
|
||||||
fn path(&self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,11 @@ mod loop_device;
|
|||||||
mod markers;
|
mod markers;
|
||||||
mod mount_stack;
|
mod mount_stack;
|
||||||
mod partition;
|
mod partition;
|
||||||
mod removeable_devices;
|
|
||||||
mod storage_device;
|
mod storage_device;
|
||||||
|
|
||||||
pub use crypt::{is_encrypted_device, EncryptedDevice};
|
pub use crypt::EncryptedDevice;
|
||||||
pub use filesystem::{Filesystem, FilesystemType};
|
pub use filesystem::{Filesystem, FilesystemType};
|
||||||
pub use loop_device::LoopDevice;
|
pub use loop_device::LoopDevice;
|
||||||
pub use markers::BlockDevice;
|
pub use markers::BlockDevice;
|
||||||
pub use mount_stack::MountStack;
|
pub use mount_stack::MountStack;
|
||||||
pub use removeable_devices::get_storage_devices;
|
|
||||||
pub use storage_device::StorageDevice;
|
pub use storage_device::StorageDevice;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::Filesystem;
|
use super::Filesystem;
|
||||||
use anyhow::anyhow;
|
use crate::error::{Error, ErrorKind};
|
||||||
|
use failure::Fail;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
|
use nix;
|
||||||
use nix::mount::{mount, umount, MsFlags};
|
use nix::mount::{mount, umount, MsFlags};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -18,6 +20,7 @@ impl<'a> MountStack<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn mount(
|
pub fn mount(
|
||||||
&mut self,
|
&mut self,
|
||||||
filesystem: &'a Filesystem,
|
filesystem: &'a Filesystem,
|
||||||
@ -37,43 +40,21 @@ impl<'a> MountStack<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_mount(
|
fn _umount(&mut self) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
source: PathBuf,
|
|
||||||
target: PathBuf,
|
|
||||||
options: Option<&str>,
|
|
||||||
) -> nix::Result<()> {
|
|
||||||
debug!("Mounting {:?} to {:?}", source, target);
|
|
||||||
mount::<_, _, str, _>(
|
|
||||||
Some(&source),
|
|
||||||
&target,
|
|
||||||
None,
|
|
||||||
MsFlags::MS_BIND | MsFlags::MS_NOATIME, // Read-only flag has no effect for bind mounts
|
|
||||||
options,
|
|
||||||
)?;
|
|
||||||
self.targets.push(target);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _umount(&mut self) -> anyhow::Result<()> {
|
|
||||||
let mut result = Ok(());
|
let mut result = Ok(());
|
||||||
|
|
||||||
while let Some(target) = self.targets.pop() {
|
while let Some(target) = self.targets.pop() {
|
||||||
debug!("Unmounting {}", target.display());
|
debug!("Unmounting {}", target.display());
|
||||||
if let Err(e) = umount(&target) {
|
if let Err(e) = umount(&target) {
|
||||||
warn!("Unable to umount {}: {}", target.display(), e);
|
warn!("Unable to umount {}: {}", target.display(), e);
|
||||||
result = Err(anyhow!(
|
result = Err(Error::from(e.context(ErrorKind::UmountFailure)));
|
||||||
"Failed unmounting filesystem: {}, {}",
|
|
||||||
target.display(),
|
|
||||||
e
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn umount(mut self) -> anyhow::Result<()> {
|
pub fn umount(mut self) -> Result<(), Error> {
|
||||||
self._umount()
|
self._umount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Partition<'a> {
|
pub struct Partition<'a> {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
origin: PhantomData<&'a dyn Origin>,
|
origin: PhantomData<&'a Origin>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Partition<'a> {
|
impl<'a> Partition<'a> {
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
use anyhow::Context;
|
|
||||||
use byte_unit::Byte;
|
|
||||||
use std::{fmt, fs};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Device {
|
|
||||||
model: String,
|
|
||||||
vendor: String,
|
|
||||||
size: Byte,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Device {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} {} ({})",
|
|
||||||
self.vendor,
|
|
||||||
self.model,
|
|
||||||
self.size.get_appropriate_unit(true)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trimmed(source: String) -> String {
|
|
||||||
String::from(source.trim_end())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_storage_devices(allow_non_removable: bool) -> anyhow::Result<Vec<Device>> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
for entry in fs::read_dir("/sys/block").context("Error querying storage devices")? {
|
|
||||||
let entry = entry.context("Error querying storage devices")?;
|
|
||||||
|
|
||||||
let removable = allow_non_removable
|
|
||||||
|| fs::read_to_string(entry.path().join("removable"))
|
|
||||||
.map(|v| v == "1\n")
|
|
||||||
.context("Error querying storage devices")?;
|
|
||||||
|
|
||||||
if !removable {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let model = fs::read_to_string(entry.path().join("device/model"))
|
|
||||||
.map(trimmed)
|
|
||||||
.context("Error querying storage devices")?;
|
|
||||||
|
|
||||||
if model == "CD-ROM" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(Device {
|
|
||||||
name: entry
|
|
||||||
.path()
|
|
||||||
.file_name()
|
|
||||||
.expect("Could not get file name for dir entry /sys/block")
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned(),
|
|
||||||
model,
|
|
||||||
vendor: fs::read_to_string(entry.path().join("device/vendor"))
|
|
||||||
.map(trimmed)
|
|
||||||
.context("Error querying storage devices")?,
|
|
||||||
size: Byte::from_bytes(
|
|
||||||
fs::read_to_string(entry.path().join("size"))
|
|
||||||
.context("Error querying storage devices")?
|
|
||||||
.trim()
|
|
||||||
.parse::<u128>()
|
|
||||||
.context("Could not parse block size to unsigned integer (u128)")?
|
|
||||||
* 512,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sanity() {
|
|
||||||
let devices = get_storage_devices(false).expect("No devices");
|
|
||||||
println!("{:?}", devices);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
use super::markers::{BlockDevice, Origin};
|
use super::markers::{BlockDevice, Origin};
|
||||||
use super::partition::Partition;
|
use super::partition::Partition;
|
||||||
use anyhow::{anyhow, Context};
|
use crate::error::{Error, ErrorKind};
|
||||||
|
use failure::ResultExt;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
@ -10,36 +11,31 @@ use std::path::{Path, PathBuf};
|
|||||||
pub struct StorageDevice<'a> {
|
pub struct StorageDevice<'a> {
|
||||||
name: String,
|
name: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
origin: PhantomData<&'a dyn Origin>,
|
origin: PhantomData<&'a Origin>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StorageDevice<'a> {
|
impl<'a> StorageDevice<'a> {
|
||||||
pub fn from_path(path: &'a Path, allow_non_removable: bool) -> anyhow::Result<Self> {
|
pub fn from_path(path: &'a Path) -> Result<Self, Error> {
|
||||||
debug!("path: {:?}", path);
|
let real_path = path.canonicalize().context(ErrorKind::DeviceQuery)?;
|
||||||
let path = path
|
let device_name = real_path
|
||||||
.canonicalize()
|
|
||||||
.context("Error querying information about the block device")?;
|
|
||||||
let device_name = path
|
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(std::ffi::OsStr::to_str)
|
.and_then(|s| s.to_str())
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
.ok_or_else(|| anyhow!("Invalid device name: {}", path.display()))?;
|
.ok_or_else(|| Error::from(ErrorKind::InvalidDeviceName))?;
|
||||||
|
|
||||||
debug!("real path: {:?}, device name: {:?}", path, device_name);
|
debug!(
|
||||||
|
"path: {:?}, real path: {:?}, device name: {:?}",
|
||||||
|
path, real_path, device_name
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(path);
|
||||||
let _self = Self {
|
let _self = Self {
|
||||||
name: device_name,
|
name: device_name,
|
||||||
path,
|
path: real_path,
|
||||||
origin: PhantomData,
|
origin: PhantomData,
|
||||||
};
|
};
|
||||||
|
if !(_self.is_removable_device()? || _self.is_loop_device()) {
|
||||||
// If we only allow removable/loop devices, and the device is neither removable or a loop
|
return Err(ErrorKind::DangerousDevice)?;
|
||||||
// device then throw a DangerousDevice error
|
|
||||||
if !(allow_non_removable || _self.is_removable_device()? || _self.is_loop_device()) {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"The given block device is neither removable nor a loop device: {}",
|
|
||||||
_self.name
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(_self)
|
Ok(_self)
|
||||||
@ -51,13 +47,12 @@ impl<'a> StorageDevice<'a> {
|
|||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_removable_device(&self) -> anyhow::Result<bool> {
|
fn is_removable_device(&self) -> Result<bool, Error> {
|
||||||
let mut path = self.sys_path();
|
let mut path = self.sys_path();
|
||||||
path.push("removable");
|
path.push("removable");
|
||||||
|
|
||||||
debug!("Reading: {:?}", path);
|
debug!("Reading: {:?}", path);
|
||||||
let result =
|
let result = read_to_string(&path).context(ErrorKind::DeviceQuery)?;
|
||||||
read_to_string(&path).context("Error querying information about the block device")?;
|
|
||||||
debug!("{:?} -> {}", path, result);
|
debug!("{:?} -> {}", path, result);
|
||||||
|
|
||||||
Ok(result == "1\n")
|
Ok(result == "1\n")
|
||||||
@ -69,15 +64,8 @@ impl<'a> StorageDevice<'a> {
|
|||||||
path.exists()
|
path.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_partition(&self, index: u8) -> anyhow::Result<Partition> {
|
pub fn get_partition(&self, index: u8) -> Result<Partition, Error> {
|
||||||
let name = if self
|
let name = if self.name.chars().rev().next().unwrap().is_digit(10) {
|
||||||
.name
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.next()
|
|
||||||
.expect("Storage device name is empty")
|
|
||||||
.is_ascii_digit()
|
|
||||||
{
|
|
||||||
format!("{}p{}", self.name, index)
|
format!("{}p{}", self.name, index)
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}", self.name, index)
|
format!("{}{}", self.name, index)
|
||||||
@ -87,7 +75,7 @@ impl<'a> StorageDevice<'a> {
|
|||||||
|
|
||||||
debug!("Partition {} for {} is in {:?}", index, self.name, path);
|
debug!("Partition {} for {} is in {:?}", index, self.name, path);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(anyhow!("Partition {} does not exist", index));
|
return Err(ErrorKind::NoSuchPartition(index).into());
|
||||||
}
|
}
|
||||||
Ok(Partition::new::<Self>(path))
|
Ok(Partition::new::<Self>(path))
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
mod chroot;
|
use super::error::*;
|
||||||
mod mount;
|
use failure::ResultExt;
|
||||||
mod qemu;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
pub use chroot::chroot;
|
|
||||||
pub use mount::mount;
|
|
||||||
pub use qemu::qemu;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use which::which;
|
use which::which;
|
||||||
@ -17,9 +10,9 @@ pub struct Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tool {
|
impl Tool {
|
||||||
pub fn find(name: &'static str) -> anyhow::Result<Self> {
|
pub fn find(name: &'static str) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
exec: which(name).context(format!("Cannot find {}", name))?,
|
exec: which(name).context(ErrorKind::NoTool(name))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1,75 +0,0 @@
|
|||||||
use super::mount;
|
|
||||||
use super::Tool;
|
|
||||||
use crate::args;
|
|
||||||
use crate::constants::{BOOT_PARTITION_INDEX, ROOT_PARTITION_INDEX};
|
|
||||||
use crate::process::CommandExt;
|
|
||||||
use crate::storage;
|
|
||||||
use crate::storage::{is_encrypted_device, EncryptedDevice};
|
|
||||||
use crate::storage::{BlockDevice, Filesystem, FilesystemType, LoopDevice};
|
|
||||||
use anyhow::Context;
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
/// Use arch-chroot to chroot to the given device
|
|
||||||
/// Also handles encrypted root partitions (detected by checking for the LUKS magic header)
|
|
||||||
pub fn chroot(command: args::ChrootCommand) -> anyhow::Result<()> {
|
|
||||||
let arch_chroot = Tool::find("arch-chroot")?;
|
|
||||||
let cryptsetup;
|
|
||||||
|
|
||||||
let loop_device: Option<LoopDevice>;
|
|
||||||
let storage_device =
|
|
||||||
match storage::StorageDevice::from_path(&command.block_device, command.allow_non_removable)
|
|
||||||
{
|
|
||||||
Ok(b) => b,
|
|
||||||
Err(_) => {
|
|
||||||
loop_device = Some(LoopDevice::create(&command.block_device)?);
|
|
||||||
storage::StorageDevice::from_path(
|
|
||||||
loop_device.as_ref().expect("loop device not found").path(),
|
|
||||||
command.allow_non_removable,
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mount_point = tempdir().context("Error creating a temporary directory")?;
|
|
||||||
|
|
||||||
let boot_partition = storage_device.get_partition(BOOT_PARTITION_INDEX)?;
|
|
||||||
let boot_filesystem = Filesystem::from_partition(&boot_partition, FilesystemType::Vfat);
|
|
||||||
|
|
||||||
let root_partition_base = storage_device.get_partition(ROOT_PARTITION_INDEX)?;
|
|
||||||
let encrypted_root = if is_encrypted_device(&root_partition_base)? {
|
|
||||||
cryptsetup = Some(Tool::find("cryptsetup")?);
|
|
||||||
Some(EncryptedDevice::open(
|
|
||||||
cryptsetup.as_ref().expect("cryptsetup not found"),
|
|
||||||
&root_partition_base,
|
|
||||||
"alma_root".into(),
|
|
||||||
)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let root_partition = if let Some(e) = encrypted_root.as_ref() {
|
|
||||||
e as &dyn BlockDevice
|
|
||||||
} else {
|
|
||||||
&root_partition_base as &dyn BlockDevice
|
|
||||||
};
|
|
||||||
let root_filesystem = Filesystem::from_partition(root_partition, FilesystemType::Ext4);
|
|
||||||
|
|
||||||
let mount_stack = mount(mount_point.path(), &boot_filesystem, &root_filesystem)?;
|
|
||||||
|
|
||||||
arch_chroot
|
|
||||||
.execute()
|
|
||||||
.arg(mount_point.path())
|
|
||||||
.args(&command.command)
|
|
||||||
.run()
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Error running command in chroot: {}",
|
|
||||||
command.command.join(" "),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
info!("Unmounting filesystems");
|
|
||||||
mount_stack.umount()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
use crate::storage::{Filesystem, MountStack};
|
|
||||||
use anyhow::Context;
|
|
||||||
use log::{debug, info};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
/// Mounts root filesystem to given mount_path
|
|
||||||
/// Mounts boot filesystem to mount_path/boot
|
|
||||||
/// Note we mount with noatime to reduce disk writes by not recording file access times
|
|
||||||
pub fn mount<'a>(
|
|
||||||
mount_path: &Path,
|
|
||||||
boot_filesystem: &'a Filesystem,
|
|
||||||
root_filesystem: &'a Filesystem,
|
|
||||||
) -> anyhow::Result<MountStack<'a>> {
|
|
||||||
let mut mount_stack = MountStack::new();
|
|
||||||
debug!(
|
|
||||||
"Root partition: {}",
|
|
||||||
root_filesystem.block().path().display()
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Mounting filesystems to {}", mount_path.display());
|
|
||||||
mount_stack
|
|
||||||
.mount(root_filesystem, mount_path.into(), None)
|
|
||||||
.with_context(|| format!("Error mounting filesystem to {}", mount_path.display()))?;
|
|
||||||
|
|
||||||
let boot_point = mount_path.join("boot");
|
|
||||||
if !boot_point.exists() {
|
|
||||||
fs::create_dir(&boot_point).context("Error creating the boot directory")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
mount_stack
|
|
||||||
.mount(boot_filesystem, boot_point, None)
|
|
||||||
.context("Error mounting the boot point")?;
|
|
||||||
|
|
||||||
Ok(mount_stack)
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use super::Tool;
|
|
||||||
use crate::args;
|
|
||||||
use anyhow::Context;
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
use std::os::unix::process::CommandExt as UnixCommandExt;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/// Loads given block device in qemu
|
|
||||||
/// Uses kvm if it is enabled
|
|
||||||
pub fn qemu(command: args::QemuCommand) -> anyhow::Result<()> {
|
|
||||||
let qemu = Tool::find("qemu-system-x86_64")?;
|
|
||||||
|
|
||||||
let mut run = qemu.execute();
|
|
||||||
run.args([
|
|
||||||
"-m",
|
|
||||||
"4G",
|
|
||||||
"-netdev",
|
|
||||||
"user,id=user.0",
|
|
||||||
"-device",
|
|
||||||
"virtio-net-pci,netdev=user.0",
|
|
||||||
"-device",
|
|
||||||
"qemu-xhci,id=xhci",
|
|
||||||
"-device",
|
|
||||||
"usb-tablet,bus=xhci.0",
|
|
||||||
"-drive",
|
|
||||||
])
|
|
||||||
.arg(format!(
|
|
||||||
"file={},if=virtio,format=raw",
|
|
||||||
command.block_device.display()
|
|
||||||
))
|
|
||||||
.args(command.args);
|
|
||||||
|
|
||||||
if PathBuf::from("/dev/kvm").exists() {
|
|
||||||
debug!("KVM is enabled");
|
|
||||||
run.args(["-enable-kvm", "-cpu", "host"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let err = run.exec();
|
|
||||||
|
|
||||||
Err(err).context("Failed launching Qemu")?
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user