Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52c324c199 | |||
| d6fa513ca7 | |||
| 4a578129b2 | |||
| 9bcded087e | |||
| 3bd7013db4 | |||
| a290059211 | |||
| 8a4014fb3c | |||
| fc1ece67b3 | |||
| 80d0eaeb21 | |||
| d87746b279 | |||
| d01da350c1 |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"sha1": "7040cf71b3a5d15d91802810d0a50aa197970c43"
|
||||||
|
},
|
||||||
|
"path_in_vcs": ""
|
||||||
|
}
|
||||||
+2
-4
@@ -1,4 +1,2 @@
|
|||||||
pkg
|
/target
|
||||||
sysroot
|
/Cargo.lock
|
||||||
/target/
|
|
||||||
/test.bin
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
image: "rust:latest"
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- lint
|
|
||||||
- test
|
|
||||||
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "master"'
|
|
||||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- rustup component add rustfmt
|
|
||||||
- cargo fmt -- --check
|
|
||||||
|
|
||||||
cargo-test:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- apt update && apt install -y fuse3 libfuse3-dev
|
|
||||||
- cargo build --locked
|
|
||||||
- cargo test
|
|
||||||
- ./target/debug/redox_installer -c res/test.toml test.bin --no-mount
|
|
||||||
Generated
-2683
File diff suppressed because it is too large
Load Diff
+31
-66
@@ -1,73 +1,38 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "redox_installer"
|
name = "libredox"
|
||||||
version = "0.2.42+rb0.3.0"
|
authors = ["4lDO2 <4lDO2@protonmail.com>", "vasilito <adminpupkin@gmail.com>"]
|
||||||
description = "A Redox filesystem builder"
|
version = "0.1.18+rb0.3.0"
|
||||||
license = "MIT"
|
|
||||||
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"]
|
|
||||||
repository = "https://gitlab.redox-os.org/redox-os/installer"
|
|
||||||
default-run = "redox_installer"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
[[bin]]
|
description = "Redox stable ABI"
|
||||||
name = "redox_installer"
|
repository = "https://gitea.redbearos.org/vasilito/RedBear-OS"
|
||||||
path = "src/bin/installer.rs"
|
exclude = ["target"]
|
||||||
required-features = ["installer"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "redox_installer_tui"
|
|
||||||
path = "src/bin/installer_tui.rs"
|
|
||||||
required-features = ["installer"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "redox_installer"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1"
|
|
||||||
arg_parser = "0.1.0"
|
|
||||||
fatfs = { version = "0.3.0", optional = true }
|
|
||||||
fscommon = { version = "0.1.1", optional = true }
|
|
||||||
gpt = { version = "3.0.0", optional = true }
|
|
||||||
libc = "0.2.70"
|
|
||||||
pkgar = { version = "0.2", optional = true }
|
|
||||||
pkgar-core = { version = "0.2", optional = true }
|
|
||||||
pkgar-keys = { version = "0.2", optional = true }
|
|
||||||
rand = { version = "0.9", optional = true }
|
|
||||||
redox-pkg = { version = "0.3", features = ["indicatif"], optional = true }
|
|
||||||
redox_syscall = { path = "../syscall", optional = true }
|
|
||||||
redoxfs = { path = "../redoxfs", optional = true, default-features = false, features = ["std", "log"] }
|
|
||||||
rust-argon2 = { version = "3", optional = true }
|
|
||||||
serde = "1"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
termion = { version = "4", optional = true }
|
|
||||||
toml = "0.8"
|
|
||||||
uuid = { version = "1.4", features = ["v4"], optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "redox")'.dependencies]
|
|
||||||
libredox = { path = "../libredox" }
|
|
||||||
ring = "=0.17.8"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["installer", "fuse"]
|
default = ["base", "call", "std", "protocol", "redox_syscall"]
|
||||||
installer = [
|
base = ["libc"]
|
||||||
"fatfs",
|
call = ["base"]
|
||||||
"fscommon",
|
std = ["base"]
|
||||||
"gpt",
|
protocol = ["plain", "bitflags"]
|
||||||
"pkgar",
|
mkns = ["ioslice"]
|
||||||
"pkgar-core",
|
# The `redox_syscall` feature activates the optional `redox_syscall`
|
||||||
"pkgar-keys",
|
# dep below. The `#[cfg(feature = "redox_syscall")]` attributes in
|
||||||
"rand",
|
# src/lib.rs gate the `From` impls between `libredox::error::Error`
|
||||||
"redox-pkg",
|
# and `syscall::Error`; without the feature, those impls are absent
|
||||||
"redox_syscall",
|
# and `?` propagation between the two types fails. Mirroring
|
||||||
"redoxfs",
|
# upstream `libredox 0.1.18`'s feature structure.
|
||||||
"rust-argon2",
|
redox_syscall = ["dep:redox_syscall"]
|
||||||
"termion",
|
|
||||||
"uuid",
|
[dependencies]
|
||||||
]
|
bitflags = { version = "2", optional = true }
|
||||||
fuse = ["redoxfs/fuse"]
|
libc = { version = "0.2", optional = true }
|
||||||
|
# The redox_syscall package has [lib] name = "syscall", so
|
||||||
|
# in Rust code it is imported as `syscall`, NOT `redox_syscall`.
|
||||||
|
# This dep is optional and gated by the `redox_syscall` feature
|
||||||
|
# above. Matching upstream `libredox 0.1.18` structure.
|
||||||
|
redox_syscall = { path = "../syscall", optional = true }
|
||||||
|
ioslice = { version = "0.6", optional = true }
|
||||||
|
plain = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# https://github.com/briansmith/ring/issues/1999
|
|
||||||
ring = { git = "https://gitlab.redox-os.org/redox-os/ring.git", branch = "redox-0.17.8" }
|
|
||||||
redox_syscall = { path = "../syscall" }
|
redox_syscall = { path = "../syscall" }
|
||||||
libredox = { path = "../libredox" }
|
|
||||||
|
|||||||
Generated
+44
@@ -0,0 +1,44 @@
|
|||||||
|
[package]
|
||||||
|
name = "libredox"
|
||||||
|
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||||
|
# Red Bear OS Phase J: version is 0.1.18 upstream. The
|
||||||
|
# redox_syscall dep is now required (not optional) because
|
||||||
|
# the local fork's acpi module (added in this commit) re-
|
||||||
|
# exports AcPiVerb from redox_syscall, and downstream recipes
|
||||||
|
# that don't enable the redox_syscall feature get an
|
||||||
|
# "unresolved import" error. Making the dep non-optional
|
||||||
|
# also matches the upstream 0.1.18 Cargo.toml pattern where
|
||||||
|
# the redox_syscall dep is unconditional.
|
||||||
|
version = "0.1.18"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
description = "Redox stable ABI"
|
||||||
|
# Red Bear OS fork lives at the canonical outer repo
|
||||||
|
# (gitea.redbearos.org/vasilito/RedBear-OS).
|
||||||
|
repository = "https://gitea.redbearos.org/vasilito/RedBear-OS"
|
||||||
|
exclude = ["target"]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["base", "call", "std", "protocol"]
|
||||||
|
base = ["libc"]
|
||||||
|
call = ["base"]
|
||||||
|
std = ["base"]
|
||||||
|
protocol = ["plain", "bitflags", "redox_syscall"]
|
||||||
|
mkns = ["ioslice"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags = { version = "2", optional = true }
|
||||||
|
libc = { version = "0.2", optional = true }
|
||||||
|
# Phase J: path override to the local fork (../syscall
|
||||||
|
# relative to the libredox fork's local/sources/libredox/
|
||||||
|
# path). This gives libredox access to the EnterS2Idle /
|
||||||
|
# ExitS2Idle AcpiVerb variants. Cargo's [patch.crates-io]
|
||||||
|
# in the workspace's outer Cargo.toml (in base/ and kernel/)
|
||||||
|
# is what wires this path through to the actual
|
||||||
|
# redox_syscall crate; this path entry is the libredox-
|
||||||
|
# side patch override for the same crate.
|
||||||
|
redox_syscall = { path = "../syscall", version = "0.8" }
|
||||||
|
ioslice = { version = "0.6", optional = true }
|
||||||
|
plain = { version = "0.2", optional = true }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 Redox OS
|
Copyright (c) 2023 4lDO2
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# Redox OS installer
|
|
||||||
|
|
||||||
The Redox installer will allow you to produce a Redox OS image. You will
|
|
||||||
be able to specify:
|
|
||||||
- Output device (raw image, ISO, QEMU, VirtualBox, drive)
|
|
||||||
- Filesystem
|
|
||||||
- Included packages
|
|
||||||
- Method of installation (from source, from binary)
|
|
||||||
- User accounts
|
|
||||||
|
|
||||||
You will be prompted to install dependencies, based on your OS and method of
|
|
||||||
installation. The easiest method is to install from binaries.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
It is recommended to compile with `cargo`, in release mode:
|
|
||||||
```bash
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, you will be prompted to supply configuration options. You can
|
|
||||||
use the scripted mode by supplying a configuration file:
|
|
||||||
```bash
|
|
||||||
cargo run --release -- config/example.toml
|
|
||||||
```
|
|
||||||
An example configuration can be found in [config/example.toml](./config/example.toml).
|
|
||||||
Unsuplied configuration will use the default. You can use the `general.prompt`
|
|
||||||
setting to prompt when configuration is not set. Multiple configurations can
|
|
||||||
be specified, they will be built in order.
|
|
||||||
|
|
||||||
## Embedding
|
|
||||||
|
|
||||||
The installer can also be used inside of other crates, as a library:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# Cargo.toml
|
|
||||||
[dependencies]
|
|
||||||
redox_installer = "0.1"
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/main.rs
|
|
||||||
extern crate redox_installer;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut config = redox_installer::Config::default();
|
|
||||||
...
|
|
||||||
redox_installer::install(config);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
# This is the default configuration file
|
|
||||||
|
|
||||||
# General settings
|
|
||||||
[general]
|
|
||||||
# Do not prompt if settings are not defined
|
|
||||||
prompt = false
|
|
||||||
|
|
||||||
# Package settings
|
|
||||||
[packages]
|
|
||||||
#acid = {}
|
|
||||||
#autoconf = {}
|
|
||||||
#automake = {}
|
|
||||||
#bash = {}
|
|
||||||
#binutils = {}
|
|
||||||
#ca-certificates = {}
|
|
||||||
#cargo = {}
|
|
||||||
#contain = {}
|
|
||||||
coreutils = {}
|
|
||||||
#curl = {}
|
|
||||||
#dash = {}
|
|
||||||
#diffutils = {}
|
|
||||||
drivers = {}
|
|
||||||
extrautils = {}
|
|
||||||
findutils = {}
|
|
||||||
#games = {}
|
|
||||||
#gawk = {}
|
|
||||||
#gcc = {}
|
|
||||||
#git = {}
|
|
||||||
#gnu-binutils = {}
|
|
||||||
#gnu-make = {}
|
|
||||||
#installer = {}
|
|
||||||
ion = {}
|
|
||||||
#lua = {}
|
|
||||||
#nasm = {}
|
|
||||||
netstack = {}
|
|
||||||
netutils = {}
|
|
||||||
#newlib = {}
|
|
||||||
#openssl = {}
|
|
||||||
orbdata = {}
|
|
||||||
orbital = {}
|
|
||||||
orbterm = {}
|
|
||||||
orbutils = {}
|
|
||||||
pastel = {}
|
|
||||||
#patch = {}
|
|
||||||
#pixelcannon = {}
|
|
||||||
pkgutils = {}
|
|
||||||
ptyd = {}
|
|
||||||
#python = {}
|
|
||||||
randd = {}
|
|
||||||
#redoxfs = {}
|
|
||||||
#rust = {}
|
|
||||||
#rustual-boy = {}
|
|
||||||
#sed = {}
|
|
||||||
smith = {}
|
|
||||||
sodium = {}
|
|
||||||
userutils = {}
|
|
||||||
uutils = {}
|
|
||||||
#xz = {}
|
|
||||||
|
|
||||||
# User settings
|
|
||||||
[users.root]
|
|
||||||
password = "password"
|
|
||||||
uid = 0
|
|
||||||
gid = 0
|
|
||||||
name = "root"
|
|
||||||
home = "/root"
|
|
||||||
|
|
||||||
[users.user]
|
|
||||||
# Password is unset
|
|
||||||
password = ""
|
|
||||||
|
|
||||||
[groups.sudo]
|
|
||||||
gid = 1
|
|
||||||
members = ["user"]
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/init.d/00_base"
|
|
||||||
data = """
|
|
||||||
pcid /etc/pcid/filesystem.toml
|
|
||||||
randd
|
|
||||||
ptyd
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/init.d/10_net"
|
|
||||||
data = """
|
|
||||||
ethernetd
|
|
||||||
ipd
|
|
||||||
icmpd
|
|
||||||
tcpd
|
|
||||||
udpd
|
|
||||||
dhcpd -b
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/init.d/20_orbital"
|
|
||||||
data = """
|
|
||||||
orbital orblogin launcher
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/init.d/30_console"
|
|
||||||
data = """
|
|
||||||
getty display/vesa:2
|
|
||||||
getty debug: -J
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/dns"
|
|
||||||
data = """
|
|
||||||
208.67.222.222
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip"
|
|
||||||
data = """
|
|
||||||
10.0.2.15
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip_router"
|
|
||||||
data = """
|
|
||||||
10.0.2.2
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip_subnet"
|
|
||||||
data = """
|
|
||||||
255.255.255.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/mac"
|
|
||||||
data = """
|
|
||||||
54-52-00-ab-cd-ef
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/pkg.d/50_redox"
|
|
||||||
data = "https://static.redox-os.org/pkg"
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/hostname"
|
|
||||||
data = "redox"
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/issue"
|
|
||||||
data = """
|
|
||||||
########## Redox OS ##########
|
|
||||||
# Login with the following: #
|
|
||||||
# `user` #
|
|
||||||
# `root`:`password` #
|
|
||||||
##############################
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/motd"
|
|
||||||
data = """
|
|
||||||
Welcome to Redox OS!
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr"
|
|
||||||
data = "/"
|
|
||||||
symlink = true
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/tmp"
|
|
||||||
data = ""
|
|
||||||
directory = true
|
|
||||||
# 0o1777
|
|
||||||
mode = 1023
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# This is the default configuration file
|
|
||||||
|
|
||||||
# General settings
|
|
||||||
[general]
|
|
||||||
# Do not prompt if settings are not defined
|
|
||||||
prompt = false
|
|
||||||
|
|
||||||
# Package settings
|
|
||||||
[packages]
|
|
||||||
binutils = {}
|
|
||||||
coreutils = {}
|
|
||||||
extrautils = {}
|
|
||||||
ion = {}
|
|
||||||
netutils = {}
|
|
||||||
pkgutils = {}
|
|
||||||
userutils = {}
|
|
||||||
|
|
||||||
# User settings
|
|
||||||
[users.root]
|
|
||||||
password = "password"
|
|
||||||
uid = 0
|
|
||||||
gid = 0
|
|
||||||
name = "root"
|
|
||||||
home = "/root"
|
|
||||||
-411
@@ -1,411 +0,0 @@
|
|||||||
# Automatically generated by update.sh
|
|
||||||
|
|
||||||
include = []
|
|
||||||
|
|
||||||
[general]
|
|
||||||
prompt = false
|
|
||||||
filesystem_size = 256
|
|
||||||
|
|
||||||
[packages.base]
|
|
||||||
|
|
||||||
[packages.base-initfs]
|
|
||||||
|
|
||||||
[packages.bootloader]
|
|
||||||
|
|
||||||
[packages.ca-certificates]
|
|
||||||
|
|
||||||
[packages.coreutils]
|
|
||||||
|
|
||||||
[packages.extrautils]
|
|
||||||
|
|
||||||
[packages.findutils]
|
|
||||||
|
|
||||||
[packages.ion]
|
|
||||||
|
|
||||||
[packages.kernel]
|
|
||||||
|
|
||||||
[packages.kibi]
|
|
||||||
|
|
||||||
[packages.libgcc]
|
|
||||||
|
|
||||||
[packages.libstdcxx]
|
|
||||||
|
|
||||||
[packages.netdb]
|
|
||||||
|
|
||||||
[packages.netutils]
|
|
||||||
|
|
||||||
[packages.pkgutils]
|
|
||||||
|
|
||||||
[packages.relibc]
|
|
||||||
|
|
||||||
[packages.userutils]
|
|
||||||
|
|
||||||
[packages.uutils]
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib/init.d/00_base"
|
|
||||||
data = """
|
|
||||||
# clear and recreate tmpdir with 0o1777 permission
|
|
||||||
rm -rf /tmp
|
|
||||||
mkdir -m a=rwxt /tmp
|
|
||||||
|
|
||||||
ipcd
|
|
||||||
ptyd
|
|
||||||
nowait sudo --daemon
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib/init.d/00_drivers"
|
|
||||||
data = """
|
|
||||||
pcid-spawner /etc/pcid.d/
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/hostname"
|
|
||||||
data = "redox"
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib/os-release"
|
|
||||||
data = """
|
|
||||||
PRETTY_NAME="Redox OS 0.9.0"
|
|
||||||
NAME="Redox OS"
|
|
||||||
VERSION_ID="0.9.0"
|
|
||||||
VERSION="0.9.0"
|
|
||||||
ID="redox-os"
|
|
||||||
|
|
||||||
HOME_URL="https://redox-os.org/"
|
|
||||||
DOCUMENTATION_URL="https://redox-os.org/docs/"
|
|
||||||
SUPPORT_URL="https://redox-os.org/community/"
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/os-release"
|
|
||||||
data = "../usr/lib/os-release"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/pkg.d/50_redox"
|
|
||||||
data = "https://static.redox-os.org/pkg"
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/bin"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/bin"
|
|
||||||
data = "usr/bin"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/include"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/include"
|
|
||||||
data = "usr/include"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/lib"
|
|
||||||
data = "usr/lib"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/libexec"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/share"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/share"
|
|
||||||
data = "usr/share"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/share/fonts"
|
|
||||||
data = "../../ui/fonts"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/cache"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/lib"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/lock"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 1023
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/log"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/run"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 493
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/var/tmp"
|
|
||||||
data = ""
|
|
||||||
symlink = false
|
|
||||||
directory = true
|
|
||||||
mode = 1023
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/null"
|
|
||||||
data = "/scheme/null"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/random"
|
|
||||||
data = "/scheme/rand"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/urandom"
|
|
||||||
data = "/scheme/rand"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/zero"
|
|
||||||
data = "/scheme/zero"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/tty"
|
|
||||||
data = "libc:tty"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/stdin"
|
|
||||||
data = "libc:stdin"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/stdout"
|
|
||||||
data = "libc:stdout"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/dev/stderr"
|
|
||||||
data = "libc:stderr"
|
|
||||||
symlink = true
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib/init.d/10_net"
|
|
||||||
data = """
|
|
||||||
smolnetd
|
|
||||||
nowait dhcpd
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/dns"
|
|
||||||
data = """
|
|
||||||
9.9.9.9
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip"
|
|
||||||
data = """
|
|
||||||
10.0.2.15
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip_router"
|
|
||||||
data = """
|
|
||||||
10.0.2.2
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/net/ip_subnet"
|
|
||||||
data = """
|
|
||||||
255.255.255.0
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/usr/lib/init.d/30_console"
|
|
||||||
data = """
|
|
||||||
inputd -A 2
|
|
||||||
nowait getty 2
|
|
||||||
nowait getty /scheme/debug -J
|
|
||||||
"""
|
|
||||||
symlink = false
|
|
||||||
directory = false
|
|
||||||
recursive_chown = false
|
|
||||||
postinstall = false
|
|
||||||
|
|
||||||
[users.root]
|
|
||||||
password = "password"
|
|
||||||
uid = 0
|
|
||||||
gid = 0
|
|
||||||
name = "root"
|
|
||||||
home = "/root"
|
|
||||||
shell = "/usr/bin/ion"
|
|
||||||
|
|
||||||
[users.user]
|
|
||||||
password = ""
|
|
||||||
shell = "/usr/bin/ion"
|
|
||||||
|
|
||||||
[groups.sudo]
|
|
||||||
gid = 1
|
|
||||||
members = ["user"]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
RES_PATH="$(dirname "$0")"
|
|
||||||
|
|
||||||
if [ -d "$1" ]
|
|
||||||
then
|
|
||||||
REDOX_PATH="$1"
|
|
||||||
else
|
|
||||||
echo "$0 [path to redox repository]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
# Update res/test.toml from the redoxer.toml template
|
|
||||||
"${REDOX_PATH}/build/fstools/bin/redox_installer" \
|
|
||||||
--config="${REDOX_PATH}/config/x86_64/minimal-net.toml" \
|
|
||||||
--output-config="${RES_PATH}/test.toml"
|
|
||||||
sed -i '1s/^/# Automatically generated by update.sh\n\n/' "${RES_PATH}/test.toml"
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
extern crate arg_parser;
|
|
||||||
extern crate redox_installer;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate toml;
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
use std::{env, fs, process};
|
|
||||||
|
|
||||||
use arg_parser::ArgParser;
|
|
||||||
|
|
||||||
use redox_installer::{Config, PackageConfig};
|
|
||||||
|
|
||||||
const HELP_STR: &str = r#"
|
|
||||||
redox_installer - Redox Installer.
|
|
||||||
Refer to link below for filesystem config reference:
|
|
||||||
https://doc.redox-os.org/book/configuration-settings.html
|
|
||||||
|
|
||||||
Using redox_installer as an installer:
|
|
||||||
redox_installer <diskpath.img> [--config=file.toml] [--write-bootloader=file.img] [--live] [--no-mount] [--skip-partition]
|
|
||||||
<diskpath.img> Disk file to write
|
|
||||||
--config Path to filesystem config TOML
|
|
||||||
--write-bootloader Path to write UEFI bootloader to in addition to the embedded ESP
|
|
||||||
--skip-partition Skip writing GPT partition tables
|
|
||||||
Use this only if you plan to use other partition tool
|
|
||||||
--live Use bootloader configured for live disk
|
|
||||||
--no-mount Use RedoxFS AR instead of FUSE to write files
|
|
||||||
--cookbook Use local Redox OS build system rather than downloading packages
|
|
||||||
|
|
||||||
Using redox_installer as a configuration parser:
|
|
||||||
redox_installer --config=file.toml [--list-packages|--filesystem-size|--output-config path]
|
|
||||||
--list-packages List packages will be installed
|
|
||||||
--filesystem-size Output filesystem size in MB
|
|
||||||
--output-config Path to write the parsed config as another TOML
|
|
||||||
"#;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut parser = ArgParser::new(4)
|
|
||||||
.add_opt("b", "cookbook")
|
|
||||||
.add_opt("c", "config")
|
|
||||||
.add_opt("o", "output-config")
|
|
||||||
.add_opt("", "write-bootloader")
|
|
||||||
.add_flag(&["skip-partition"])
|
|
||||||
.add_flag(&["filesystem-size"])
|
|
||||||
.add_flag(&["r", "repo-binary"]) // TODO: Remove
|
|
||||||
.add_flag(&["l", "list-packages"])
|
|
||||||
.add_flag(&["live"])
|
|
||||||
.add_flag(&["no-mount"]);
|
|
||||||
parser.parse(env::args());
|
|
||||||
|
|
||||||
let skip_partition = parser.found("skip-partition");
|
|
||||||
|
|
||||||
let mut config = if let Some(path) = parser.get_opt("config") {
|
|
||||||
match Config::from_file(Path::new(&path)) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("installer: {err}");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
redox_installer::Config::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get toml of merged config
|
|
||||||
let merged_toml = toml::to_string_pretty(&config).unwrap();
|
|
||||||
|
|
||||||
// Just output merged config and exit
|
|
||||||
if let Some(path) = parser.get_opt("output-config") {
|
|
||||||
fs::write(path, merged_toml).unwrap();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add filesystem.toml to config
|
|
||||||
config.files.push(redox_installer::FileConfig {
|
|
||||||
path: "filesystem.toml".to_string(),
|
|
||||||
data: merged_toml,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
if skip_partition {
|
|
||||||
config.general.skip_partitions = Some(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if parser.found("filesystem-size") {
|
|
||||||
println!("{}", config.general.filesystem_size.unwrap_or(0));
|
|
||||||
} else if parser.found("list-packages") {
|
|
||||||
// List the packages that should be fetched or built by the cookbook
|
|
||||||
for (packagename, package) in &config.packages {
|
|
||||||
match package {
|
|
||||||
PackageConfig::Build(rule) if rule == "ignore" => {
|
|
||||||
// skip this package
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("{}", packagename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let cookbook = if let Some(path) = parser.get_opt("cookbook") {
|
|
||||||
if !Path::new(&path).is_dir() {
|
|
||||||
eprintln!("installer: {}: cookbook not found", path);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if cookbook.is_some() {
|
|
||||||
config.general.cookbook = cookbook;
|
|
||||||
}
|
|
||||||
if parser.found("live") {
|
|
||||||
config.general.live_disk = Some(true);
|
|
||||||
}
|
|
||||||
if parser.found("no-mount") {
|
|
||||||
config.general.no_mount = Some(true);
|
|
||||||
}
|
|
||||||
let write_bootloader = parser.get_opt("write-bootloader");
|
|
||||||
if write_bootloader.is_some() {
|
|
||||||
config.general.write_bootloader = write_bootloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(path) = parser.args.first() {
|
|
||||||
if let Err(err) = redox_installer::install(config, path) {
|
|
||||||
eprintln!("installer: failed to install: {:?}", err);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprint!("{}", HELP_STR);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
use anyhow::{anyhow, bail, Result};
|
|
||||||
use pkgar::{ext::EntryExt, PackageHead};
|
|
||||||
use pkgar_core::PackageSrc;
|
|
||||||
use pkgar_keys::PublicKeyFile;
|
|
||||||
use redox_installer::{try_fast_install, with_redoxfs_mount, with_whole_disk, Config, DiskOption};
|
|
||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
io::{self, Read, Write},
|
|
||||||
os::unix::fs::{symlink, MetadataExt, OpenOptionsExt},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This is not the TUI a regular user would expect it does
|
|
||||||
// 1. Linux: Implement disk listing, use "dd" to write into whole disk
|
|
||||||
// 2. Allow partitioning to allow dual boot, possibly an integration with systemd-boot/grub
|
|
||||||
// 3. Prompt everything (disk password, users, preconfigured packages, import from existing img)
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
|
||||||
fn disk_paths(_paths: &mut Vec<(PathBuf, u64)>) {}
|
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
|
||||||
fn disk_paths(paths: &mut Vec<(PathBuf, u64)>) {
|
|
||||||
let mut schemes = Vec::new();
|
|
||||||
match fs::read_dir("/scheme") {
|
|
||||||
Ok(entries) => {
|
|
||||||
for entry_res in entries {
|
|
||||||
if let Ok(entry) = entry_res {
|
|
||||||
if let Ok(file_name) = entry.file_name().into_string() {
|
|
||||||
if file_name.starts_with("disk") {
|
|
||||||
schemes.push(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("redox_installer_tui: failed to list schemes: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for scheme in schemes {
|
|
||||||
if scheme.is_dir() {
|
|
||||||
match fs::read_dir(&scheme) {
|
|
||||||
Ok(entries) => {
|
|
||||||
for entry_res in entries {
|
|
||||||
if let Ok(entry) = entry_res {
|
|
||||||
if let Ok(file_name) = entry.file_name().into_string() {
|
|
||||||
if file_name.contains('p') {
|
|
||||||
// Skip partitions
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(metadata) = entry.metadata() {
|
|
||||||
let size = metadata.len();
|
|
||||||
if size > 0 {
|
|
||||||
paths.push((entry.path(), size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"redox_installer_tui: failed to list '{}': {}",
|
|
||||||
scheme.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const KIB: u64 = 1024;
|
|
||||||
const MIB: u64 = 1024 * KIB;
|
|
||||||
const GIB: u64 = 1024 * MIB;
|
|
||||||
const TIB: u64 = 1024 * GIB;
|
|
||||||
|
|
||||||
fn format_size(size: u64) -> String {
|
|
||||||
if size >= 4 * TIB {
|
|
||||||
format!("{:.1} TiB", size as f64 / TIB as f64)
|
|
||||||
} else if size >= GIB {
|
|
||||||
format!("{:.1} GiB", size as f64 / GIB as f64)
|
|
||||||
} else if size >= MIB {
|
|
||||||
format!("{:.1} MiB", size as f64 / MIB as f64)
|
|
||||||
} else if size >= KIB {
|
|
||||||
format!("{:.1} KiB", size as f64 / KIB as f64)
|
|
||||||
} else {
|
|
||||||
format!("{} B", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<()> {
|
|
||||||
if let Some(parent) = dest.parent() {
|
|
||||||
// Parent may be a symlink
|
|
||||||
if !parent.is_symlink() {
|
|
||||||
match fs::create_dir_all(&parent) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to create directory {}: {}", parent.display(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = match fs::symlink_metadata(&src) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to read metadata of {}: {}", src.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if metadata.file_type().is_symlink() {
|
|
||||||
let real_src = match fs::read_link(&src) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to read link {}: {}", src.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match symlink(&real_src, &dest) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(err) => {
|
|
||||||
bail!(
|
|
||||||
"failed to copy link {} ({}) to {}: {}",
|
|
||||||
src.display(),
|
|
||||||
real_src.display(),
|
|
||||||
dest.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut src_file = match fs::File::open(&src) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to open file {}: {}", src.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut dest_file = match fs::OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create_new(true)
|
|
||||||
.mode(metadata.mode())
|
|
||||||
.open(&dest)
|
|
||||||
{
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to create file {}: {}", dest.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let count = match src_file.read(buf) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to read file {}: {}", src.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
match dest_file.write_all(&buf[..count]) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to write file {}: {}", dest.display(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn package_files(
|
|
||||||
root_path: &Path,
|
|
||||||
config: &mut Config,
|
|
||||||
files: &mut Vec<String>,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
//TODO: Remove packages from config where all files are located (and have valid shasum?)
|
|
||||||
config.packages.clear();
|
|
||||||
|
|
||||||
let pkey_path = "pkg/id_ed25519.pub.toml";
|
|
||||||
let pkey = PublicKeyFile::open(&root_path.join(pkey_path))?.pkey;
|
|
||||||
files.push(pkey_path.to_string());
|
|
||||||
|
|
||||||
for item_res in fs::read_dir(&root_path.join("pkg"))? {
|
|
||||||
let item = item_res?;
|
|
||||||
let pkg_path = item.path();
|
|
||||||
if pkg_path.extension() == Some(OsStr::new("pkgar_head")) {
|
|
||||||
let mut pkg = PackageHead::new(&pkg_path, &root_path, &pkey)?;
|
|
||||||
for entry in pkg.read_entries()? {
|
|
||||||
files.push(entry.check_path()?.to_str().unwrap().to_string());
|
|
||||||
}
|
|
||||||
files.push(
|
|
||||||
pkg_path
|
|
||||||
.strip_prefix(root_path)
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn choose_disk() -> PathBuf {
|
|
||||||
let mut paths = Vec::new();
|
|
||||||
disk_paths(&mut paths);
|
|
||||||
loop {
|
|
||||||
for (i, (path, size)) in paths.iter().enumerate() {
|
|
||||||
eprintln!(
|
|
||||||
"\x1B[1m{}\x1B[0m: {}: {}",
|
|
||||||
i + 1,
|
|
||||||
path.display(),
|
|
||||||
format_size(*size)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if paths.is_empty() {
|
|
||||||
eprintln!("redox_installer_tui: no RedoxFS partition found");
|
|
||||||
eprintln!("redox_installer_tui: this tool is used to overwrite unmounted RedoxFS disk in Redox OS");
|
|
||||||
process::exit(1);
|
|
||||||
} else {
|
|
||||||
eprint!("Select a drive from 1 to {}: ", paths.len());
|
|
||||||
|
|
||||||
let mut line = String::new();
|
|
||||||
match io::stdin().read_line(&mut line) {
|
|
||||||
Ok(0) => {
|
|
||||||
eprintln!("redox_installer_tui: failed to read line: end of input");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("redox_installer_tui: failed to read line: {}", err);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match line.trim().parse::<usize>() {
|
|
||||||
Ok(i) => {
|
|
||||||
if i >= 1 && i <= paths.len() {
|
|
||||||
break paths[i - 1].0.clone();
|
|
||||||
} else {
|
|
||||||
eprintln!("{} not from 1 to {}", i, paths.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("invalid input: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let root_path = Path::new("/");
|
|
||||||
|
|
||||||
let disk_path = choose_disk();
|
|
||||||
|
|
||||||
let Ok(password_opt) = redox_installer::prompt_password(
|
|
||||||
"redox_installer_tui: redoxfs password (empty for none)",
|
|
||||||
"redox_installer_tui: confirm password",
|
|
||||||
) else {
|
|
||||||
process::exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
let instant = std::time::Instant::now();
|
|
||||||
|
|
||||||
let bootloader_bios = {
|
|
||||||
let path = root_path.join("usr/lib/boot/bootloader.bios");
|
|
||||||
if path.exists() {
|
|
||||||
match fs::read(&path) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"redox_installer_tui: {}: failed to read: {}",
|
|
||||||
path.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let bootloader_efi = {
|
|
||||||
let path = root_path.join("usr/lib/boot/bootloader.efi");
|
|
||||||
if path.exists() {
|
|
||||||
match fs::read(&path) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"redox_installer_tui: {}: failed to read: {}",
|
|
||||||
path.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let disk_option = DiskOption {
|
|
||||||
bootloader_bios: &bootloader_bios,
|
|
||||||
bootloader_efi: &bootloader_efi,
|
|
||||||
password_opt: password_opt.as_ref().map(|x| x.as_bytes()),
|
|
||||||
efi_partition_size: None,
|
|
||||||
skip_partitions: false, // TODO?
|
|
||||||
};
|
|
||||||
let res = with_whole_disk(&disk_path, &disk_option, |mut fs| {
|
|
||||||
// Fast install method via filesystem clone
|
|
||||||
let mut last_percent = 0;
|
|
||||||
if try_fast_install(&mut fs, move |used, used_old| {
|
|
||||||
let percent = (used * 100) / used_old;
|
|
||||||
if percent != last_percent {
|
|
||||||
eprint!(
|
|
||||||
"\r{}%: {} MB/{} MB",
|
|
||||||
percent,
|
|
||||||
used / 1000 / 1000,
|
|
||||||
used_old / 1000 / 1000
|
|
||||||
);
|
|
||||||
last_percent = percent;
|
|
||||||
}
|
|
||||||
})? {
|
|
||||||
eprintln!("\rfinished installing using fast mode");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow install method via file copy
|
|
||||||
with_redoxfs_mount(fs, None, |mount_path| {
|
|
||||||
let mut config: Config = Config::from_file(&root_path.join("filesystem.toml"))?;
|
|
||||||
|
|
||||||
// Copy filesystem.toml, which is not packaged
|
|
||||||
let mut files = vec!["filesystem.toml".to_string()];
|
|
||||||
|
|
||||||
// Copy files from locally installed packages
|
|
||||||
package_files(&root_path, &mut config, &mut files)
|
|
||||||
// TODO: implement Error trait
|
|
||||||
.map_err(|err| anyhow!("failed to read package files: {err}"))?;
|
|
||||||
|
|
||||||
// Perform config install (after packages have been converted to files)
|
|
||||||
eprintln!("configuring system");
|
|
||||||
let cookbook: Option<&'static str> = None;
|
|
||||||
redox_installer::install_dir(config, mount_path, cookbook)
|
|
||||||
.map_err(|err| io::Error::other(err))?;
|
|
||||||
|
|
||||||
// Sort and remove duplicates
|
|
||||||
files.sort();
|
|
||||||
files.dedup();
|
|
||||||
|
|
||||||
// Install files
|
|
||||||
let mut buf = vec![0; 4 * MIB as usize];
|
|
||||||
for (i, name) in files.iter().enumerate() {
|
|
||||||
eprintln!("copy {} [{}/{}]", name, i, files.len());
|
|
||||||
|
|
||||||
let src = root_path.join(name);
|
|
||||||
let dest = mount_path.join(name);
|
|
||||||
copy_file(&src, &dest, &mut buf)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("finished installing, unmounting filesystem");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(()) => {
|
|
||||||
eprintln!(
|
|
||||||
"redox_installer_tui: installed successfully in {:?}",
|
|
||||||
instant.elapsed()
|
|
||||||
);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("redox_installer_tui: failed to install: {:?}", err);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct FileConfig {
|
|
||||||
pub path: String,
|
|
||||||
pub data: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub symlink: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub directory: bool,
|
|
||||||
pub mode: Option<u32>,
|
|
||||||
pub uid: Option<u32>,
|
|
||||||
pub gid: Option<u32>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub recursive_chown: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub postinstall: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileConfig {
|
|
||||||
pub fn new_file(path: String, data: String) -> FileConfig {
|
|
||||||
FileConfig {
|
|
||||||
path,
|
|
||||||
data,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_directory(path: String) -> FileConfig {
|
|
||||||
FileConfig {
|
|
||||||
path,
|
|
||||||
data: String::new(),
|
|
||||||
directory: true,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
|
|
||||||
self.mode = Some(mode);
|
|
||||||
self.uid = Some(uid);
|
|
||||||
self.gid = Some(gid);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_recursive_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
|
|
||||||
self.with_mod(mode, uid, gid);
|
|
||||||
self.recursive_chown = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FileConfig {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.path)?;
|
|
||||||
if self.symlink {
|
|
||||||
write!(f, " -> {}", self.data)?;
|
|
||||||
} else if self.directory {
|
|
||||||
write!(f, " type=dir")?;
|
|
||||||
if self.recursive_chown {
|
|
||||||
write!(f, " chown=yes")?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
" size={}B",
|
|
||||||
arg_parser::to_human_readable_string(self.data.len() as u64)
|
|
||||||
)?;
|
|
||||||
if self.postinstall {
|
|
||||||
write!(f, "!")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(uid) = self.uid {
|
|
||||||
write!(f, " uid={}", uid)?;
|
|
||||||
}
|
|
||||||
if let Some(uid) = self.uid {
|
|
||||||
write!(f, " gid={}", uid)?;
|
|
||||||
}
|
|
||||||
if let Some(mode) = self.mode {
|
|
||||||
write!(f, " mode={:3o}", mode)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
use anyhow::{Context, Result};
|
|
||||||
use libc::{gid_t, uid_t};
|
|
||||||
|
|
||||||
use std::ffi::{CString, OsStr};
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use std::io::{Error, Write};
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
use std::os::unix::fs::{symlink, PermissionsExt};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
|
|
||||||
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
|
|
||||||
return Err(Error::last_os_error().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive && path.is_dir() {
|
|
||||||
for entry_res in fs::read_dir(path)? {
|
|
||||||
let entry = entry_res?;
|
|
||||||
chown(entry.path(), uid, gid, recursive)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Rewrite impls
|
|
||||||
impl crate::FileConfig {
|
|
||||||
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
|
|
||||||
let path = self.path.trim_start_matches('/');
|
|
||||||
let target_file = prefix.as_ref().join(path);
|
|
||||||
|
|
||||||
if self.directory {
|
|
||||||
println!("Create directory {}", target_file.display());
|
|
||||||
fs::create_dir_all(&target_file)
|
|
||||||
.with_context(|| format!("failed to create directory {}", target_file.display()))?;
|
|
||||||
self.apply_perms(&target_file)?;
|
|
||||||
return Ok(());
|
|
||||||
} else if let Some(parent) = target_file.parent() {
|
|
||||||
println!("Create file parent {}", parent.display());
|
|
||||||
fs::create_dir_all(parent)
|
|
||||||
.with_context(|| format!("failed to create file parent {}", parent.display()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.symlink {
|
|
||||||
println!("Create symlink {} to {}", target_file.display(), self.data);
|
|
||||||
if target_file.is_symlink() {
|
|
||||||
fs::remove_file(&target_file).with_context(|| {
|
|
||||||
format!("failed to remove old symlink {}", target_file.display())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
symlink(&OsStr::new(&self.data), &target_file).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to create symlink {} to {}",
|
|
||||||
target_file.display(),
|
|
||||||
self.data
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
println!("Create file {}", target_file.display());
|
|
||||||
let mut file = File::create(&target_file)
|
|
||||||
.with_context(|| format!("failed to create file {}", target_file.display()))?;
|
|
||||||
file.write_all(self.data.as_bytes())?;
|
|
||||||
|
|
||||||
self.apply_perms(target_file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
|
||||||
let path = target.as_ref();
|
|
||||||
let mode = self
|
|
||||||
.mode
|
|
||||||
.unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
|
|
||||||
let uid = self.uid.unwrap_or(!0);
|
|
||||||
let gid = self.gid.unwrap_or(!0);
|
|
||||||
|
|
||||||
// chmod
|
|
||||||
fs::set_permissions(path, fs::Permissions::from_mode(mode))
|
|
||||||
.with_context(|| format!("failed to set permissions on {}", path.display()))?;
|
|
||||||
|
|
||||||
// chown
|
|
||||||
chown(path, uid, gid, self.recursive_chown)
|
|
||||||
.with_context(|| format!("failed to chown {}", path.display()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct GeneralConfig {
|
|
||||||
/// Specify a path where cookbook exists, all packages will be installed locally
|
|
||||||
pub cookbook: Option<String>,
|
|
||||||
/// Allow prompts for missing information such as user password
|
|
||||||
pub prompt: Option<bool>,
|
|
||||||
/// Total filesystem size in MB
|
|
||||||
pub filesystem_size: Option<u32>,
|
|
||||||
/// EFI partition size in MB, default to 2MB
|
|
||||||
pub efi_partition_size: Option<u32>,
|
|
||||||
/// Skip disk partitioning, assume whole disk is a partition
|
|
||||||
pub skip_partitions: Option<bool>,
|
|
||||||
/// Set a plain text password to encrypt the disk
|
|
||||||
pub encrypt_disk: Option<String>,
|
|
||||||
/// Use live disk for bootloader config, default is false
|
|
||||||
pub live_disk: Option<bool>,
|
|
||||||
/// If set, write bootloader disk into this path
|
|
||||||
pub write_bootloader: Option<String>,
|
|
||||||
/// Use AR to write files instead of FUSE-based mount
|
|
||||||
/// (bypasses FUSE, but slower and requires namespaced context such as "podman unshare")
|
|
||||||
pub no_mount: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GeneralConfig {
|
|
||||||
/// Merge two config, "other" is more dominant
|
|
||||||
pub(super) fn merge(&mut self, other: GeneralConfig) {
|
|
||||||
if let Some(cookbook) = other.cookbook {
|
|
||||||
self.cookbook = Some(cookbook);
|
|
||||||
}
|
|
||||||
self.filesystem_size = other.filesystem_size.or(self.filesystem_size);
|
|
||||||
self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size);
|
|
||||||
self.skip_partitions = other.skip_partitions.or(self.skip_partitions);
|
|
||||||
if let Some(encrypt_disk) = other.encrypt_disk {
|
|
||||||
self.encrypt_disk = Some(encrypt_disk);
|
|
||||||
}
|
|
||||||
self.live_disk = other.live_disk.or(self.live_disk);
|
|
||||||
if let Some(write_bootloader) = other.write_bootloader {
|
|
||||||
self.write_bootloader = Some(write_bootloader);
|
|
||||||
}
|
|
||||||
self.no_mount = other.no_mount.or(self.no_mount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fs;
|
|
||||||
use std::mem;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::PackageConfig;
|
|
||||||
|
|
||||||
pub mod file;
|
|
||||||
#[cfg(feature = "installer")]
|
|
||||||
pub mod file_impl;
|
|
||||||
pub mod general;
|
|
||||||
pub mod package;
|
|
||||||
pub mod user;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct Config {
|
|
||||||
#[serde(default)]
|
|
||||||
pub include: Vec<PathBuf>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub general: general::GeneralConfig,
|
|
||||||
#[serde(default)]
|
|
||||||
pub packages: BTreeMap<String, package::PackageConfig>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub files: Vec<file::FileConfig>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub users: BTreeMap<String, user::UserConfig>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub groups: BTreeMap<String, user::GroupConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
/// Load installer config from a TOML path
|
|
||||||
pub fn from_file(path: &Path) -> Result<Self> {
|
|
||||||
let mut config: Config = match fs::read_to_string(&path) {
|
|
||||||
Ok(config_data) => match toml::from_str(&config_data) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to decode '{}': {}", path.display(), err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
bail!("failed to read '{}': {}", path.display(), err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let config_dir = path.parent().unwrap();
|
|
||||||
|
|
||||||
let mut configs = mem::take(&mut config.include)
|
|
||||||
.into_iter()
|
|
||||||
.map(|path| {
|
|
||||||
Config::from_file(&config_dir.join(&path))
|
|
||||||
.with_context(|| format!("Importing from {}", path.display()))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Config>>>()?;
|
|
||||||
configs.push(config); // Put ourself last to ensure that it overwrites anything else.
|
|
||||||
|
|
||||||
config = configs.remove(0);
|
|
||||||
|
|
||||||
for other_config in configs {
|
|
||||||
config.merge(other_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load hardcoded install config to fetch bootloaders
|
|
||||||
pub fn bootloader_config() -> Self {
|
|
||||||
let mut bootloader_config = Config::default();
|
|
||||||
// TODO: This is unused
|
|
||||||
bootloader_config.files.push(file::FileConfig {
|
|
||||||
path: "/etc/pkg.d/50_redox".to_string(),
|
|
||||||
data: "https://static.redox-os.org/pkg".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
bootloader_config
|
|
||||||
.packages
|
|
||||||
.insert("bootloader".to_string(), PackageConfig::default());
|
|
||||||
bootloader_config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge(&mut self, other: Config) {
|
|
||||||
assert!(self.include.is_empty());
|
|
||||||
assert!(other.include.is_empty());
|
|
||||||
|
|
||||||
let Config {
|
|
||||||
include: _,
|
|
||||||
general: other_general,
|
|
||||||
packages: other_packages,
|
|
||||||
files: other_files,
|
|
||||||
users: other_users,
|
|
||||||
groups: other_groups,
|
|
||||||
} = other;
|
|
||||||
|
|
||||||
self.general.merge(other_general);
|
|
||||||
|
|
||||||
for (package, package_config) in other_packages {
|
|
||||||
self.packages.insert(package, package_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.files.extend(other_files);
|
|
||||||
|
|
||||||
for (user, user_config) in other_users {
|
|
||||||
self.users.insert(user, user_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (group, group_config) in other_groups {
|
|
||||||
self.groups.insert(group, group_config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Config {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
writeln!(f, "files:")?;
|
|
||||||
for file in &self.files {
|
|
||||||
writeln!(f, "- {}", file)?;
|
|
||||||
}
|
|
||||||
writeln!(f, "users:")?;
|
|
||||||
for (name, user) in &self.users {
|
|
||||||
writeln!(f, "- {}:{}", name, user)?;
|
|
||||||
}
|
|
||||||
write!(f, "packages: ")?;
|
|
||||||
for name in self.packages.keys() {
|
|
||||||
write!(f, " {}", name)?;
|
|
||||||
}
|
|
||||||
writeln!(f, "")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum PackageConfig {
|
|
||||||
Empty,
|
|
||||||
Build(String),
|
|
||||||
|
|
||||||
// TODO: Sum type
|
|
||||||
Spec {
|
|
||||||
version: Option<String>,
|
|
||||||
git: Option<String>,
|
|
||||||
path: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PackageConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct UserConfig {
|
|
||||||
pub password: Option<String>,
|
|
||||||
pub uid: Option<u32>,
|
|
||||||
pub gid: Option<u32>,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub home: Option<String>,
|
|
||||||
pub shell: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct GroupConfig {
|
|
||||||
pub gid: Option<u32>,
|
|
||||||
// FIXME move this to the UserConfig struct as extra_groups
|
|
||||||
pub members: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for UserConfig {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
if let Some(uid) = &self.uid {
|
|
||||||
write!(f, " uid={}", uid)?;
|
|
||||||
}
|
|
||||||
if let Some(gid) = &self.gid {
|
|
||||||
write!(f, " gid={}", gid)?;
|
|
||||||
}
|
|
||||||
if let Some(name) = &self.name {
|
|
||||||
write!(f, " name={}", name)?;
|
|
||||||
}
|
|
||||||
if let Some(home) = &self.home {
|
|
||||||
write!(f, " home={}", home)?;
|
|
||||||
}
|
|
||||||
if let Some(shell) = &self.shell {
|
|
||||||
write!(f, " shell={}", shell)?;
|
|
||||||
}
|
|
||||||
if self.password.as_ref().is_some_and(|s| !s.is_empty()) {
|
|
||||||
write!(f, " password=yes")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cmp,
|
|
||||||
convert::TryInto,
|
|
||||||
fs::{File, OpenOptions},
|
|
||||||
io::{Read, Result, Seek, SeekFrom, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DiskWrapper {
|
|
||||||
disk: File,
|
|
||||||
size: u64,
|
|
||||||
block: Box<[u8]>,
|
|
||||||
seek: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Buffer<'a> {
|
|
||||||
Read(&'a mut [u8]),
|
|
||||||
Write(&'a [u8]),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskWrapper {
|
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
let disk = OpenOptions::new().read(true).write(true).open(path)?;
|
|
||||||
let metadata = disk.metadata()?;
|
|
||||||
let size = metadata.len();
|
|
||||||
// TODO: get real block size: disk_metadata.blksize() works on disks but not image files
|
|
||||||
let block_size = 512;
|
|
||||||
let block = vec![0u8; block_size].into_boxed_slice();
|
|
||||||
Ok(Self {
|
|
||||||
disk,
|
|
||||||
size,
|
|
||||||
block,
|
|
||||||
seek: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_size(&self) -> usize {
|
|
||||||
self.block.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> u64 {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn io<'a>(&mut self, buf: &mut Buffer<'a>) -> Result<usize> {
|
|
||||||
let buf_len = match buf {
|
|
||||||
Buffer::Read(read) => read.len(),
|
|
||||||
Buffer::Write(write) => write.len(),
|
|
||||||
};
|
|
||||||
let block_len: u64 = self.block.len().try_into().unwrap();
|
|
||||||
|
|
||||||
// Do aligned I/O quickly
|
|
||||||
if self.seek % block_len == 0 && buf_len as u64 % block_len == 0 {
|
|
||||||
self.disk.seek(SeekFrom::Start(self.seek))?;
|
|
||||||
match buf {
|
|
||||||
Buffer::Read(read) => self.disk.read_exact(read)?,
|
|
||||||
Buffer::Write(write) => self.disk.write_all(write)?,
|
|
||||||
}
|
|
||||||
self.seek = self.seek.checked_add(buf_len.try_into().unwrap()).unwrap();
|
|
||||||
return Ok(buf_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
while i < buf_len {
|
|
||||||
let block = self.seek / block_len;
|
|
||||||
let offset: usize = (self.seek % block_len).try_into().unwrap();
|
|
||||||
let remaining = buf_len.checked_sub(i).unwrap();
|
|
||||||
let len = cmp::min(remaining, self.block.len().checked_sub(offset).unwrap());
|
|
||||||
|
|
||||||
self.disk
|
|
||||||
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
|
|
||||||
self.disk.read_exact(&mut self.block)?;
|
|
||||||
|
|
||||||
match buf {
|
|
||||||
Buffer::Read(read) => {
|
|
||||||
read[i..i.checked_add(len).unwrap()]
|
|
||||||
.copy_from_slice(&self.block[offset..offset.checked_add(len).unwrap()]);
|
|
||||||
}
|
|
||||||
Buffer::Write(write) => {
|
|
||||||
self.block[offset..offset.checked_add(len).unwrap()]
|
|
||||||
.copy_from_slice(&write[i..i.checked_add(len).unwrap()]);
|
|
||||||
|
|
||||||
self.disk
|
|
||||||
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
|
|
||||||
self.disk.write_all(&mut self.block)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i = i.checked_add(len).unwrap();
|
|
||||||
self.seek = self.seek.checked_add(len.try_into().unwrap()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for DiskWrapper {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
|
||||||
self.io(&mut Buffer::Read(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seek for DiskWrapper {
|
|
||||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
|
|
||||||
let current: i64 = self.seek.try_into().unwrap();
|
|
||||||
let end: i64 = self.size.try_into().unwrap();
|
|
||||||
self.seek = match pos {
|
|
||||||
SeekFrom::Start(offset) => cmp::min(self.size, offset),
|
|
||||||
SeekFrom::End(offset) => cmp::max(0, cmp::min(end, end.wrapping_add(offset))) as u64,
|
|
||||||
SeekFrom::Current(offset) => {
|
|
||||||
cmp::max(0, cmp::min(end, current.wrapping_add(offset))) as u64
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(self.seek)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for DiskWrapper {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
self.io(&mut Buffer::Write(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<()> {
|
|
||||||
self.disk.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,855 +0,0 @@
|
|||||||
use anyhow::Context;
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use pkg::Library;
|
|
||||||
use rand::{rngs::OsRng, TryRngCore};
|
|
||||||
use redoxfs::{unmount_path, Disk, DiskIo, FileSystem, BLOCK_SIZE};
|
|
||||||
use termion::input::TermRead;
|
|
||||||
|
|
||||||
use crate::config::file::FileConfig;
|
|
||||||
use crate::config::package::PackageConfig;
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::disk_wrapper::DiskWrapper;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::BTreeMap,
|
|
||||||
env, fs,
|
|
||||||
io::{self, Seek, SeekFrom, Write},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process,
|
|
||||||
rc::Rc,
|
|
||||||
sync::mpsc::channel,
|
|
||||||
thread,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct DiskOption<'a> {
|
|
||||||
pub bootloader_bios: &'a [u8],
|
|
||||||
pub bootloader_efi: &'a [u8],
|
|
||||||
pub password_opt: Option<&'a [u8]>,
|
|
||||||
pub efi_partition_size: Option<u32>, //MiB
|
|
||||||
pub skip_partitions: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_target() -> String {
|
|
||||||
// TODO: Configurable from filesystem config?
|
|
||||||
env::var("TARGET").unwrap_or(
|
|
||||||
option_env!("TARGET").map_or("x86_64-unknown-redox".to_string(), |x| x.to_string()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a password to a serialized argon2rs hash, understandable
|
|
||||||
/// by redox_users. If the password is blank, the hash is blank.
|
|
||||||
fn hash_password(password: &str) -> Result<String> {
|
|
||||||
if !password.is_empty() {
|
|
||||||
let salt = format!("{:X}", OsRng.try_next_u64()?);
|
|
||||||
let config = argon2::Config::default();
|
|
||||||
let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
|
|
||||||
Ok(hash)
|
|
||||||
} else {
|
|
||||||
Ok("".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn syscall_error(err: syscall::Error) -> io::Error {
|
|
||||||
io::Error::from_raw_os_error(err.errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a password collected from the user (plaintext)
|
|
||||||
pub fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<Option<String>> {
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let mut stdin = stdin.lock();
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
print!("{}", prompt);
|
|
||||||
let mut password = stdin.read_passwd(&mut stdout)?;
|
|
||||||
if let Some(password) = password.as_mut() {
|
|
||||||
*password = password.trim().to_string();
|
|
||||||
}
|
|
||||||
password.take_if(|s| s.is_empty());
|
|
||||||
|
|
||||||
if password.is_none() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("\n{}", confirm_prompt);
|
|
||||||
let confirm_password = stdin.read_passwd(&mut stdout)?;
|
|
||||||
|
|
||||||
// Note: Actually comparing two Option<String> values
|
|
||||||
if confirm_password == password {
|
|
||||||
return Ok(password);
|
|
||||||
} else if i < 2 {
|
|
||||||
eprintln!("passwords do not match, please try again");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bail!("passwords do not match, giving up");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_packages(config: &Config, dest: &Path, cookbook: Option<&str>) -> anyhow::Result<()> {
|
|
||||||
let target = &get_target();
|
|
||||||
|
|
||||||
let packages: Vec<&String> = config
|
|
||||||
.packages
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(packagename, package)| match package {
|
|
||||||
PackageConfig::Build(rule) if rule == "ignore" => None,
|
|
||||||
_ => Some(packagename),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut library = if let Some(cookbook) = cookbook {
|
|
||||||
let callback = pkg::callback::PlainCallback::new();
|
|
||||||
let repo = Path::new(cookbook).join("repo");
|
|
||||||
let pubkey = Path::new(cookbook).join("build");
|
|
||||||
Library::new_local(
|
|
||||||
repo,
|
|
||||||
pubkey,
|
|
||||||
dest.to_path_buf(),
|
|
||||||
target,
|
|
||||||
Rc::new(RefCell::new(callback)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let callback = pkg::callback::IndicatifCallback::new();
|
|
||||||
Library::new_remote(
|
|
||||||
&vec!["https://static.redox-os.org/pkg"],
|
|
||||||
dest,
|
|
||||||
target,
|
|
||||||
Rc::new(RefCell::new(callback)),
|
|
||||||
)
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let packages = pkg::PackageName::from_list(packages)?;
|
|
||||||
library.install(packages)?;
|
|
||||||
library.apply()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_dir(
|
|
||||||
config: Config,
|
|
||||||
output_dir: impl AsRef<Path>,
|
|
||||||
cookbook: Option<&str>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let output_dir = output_dir.as_ref();
|
|
||||||
|
|
||||||
let output_dir = output_dir.to_owned();
|
|
||||||
|
|
||||||
for file in &config.files {
|
|
||||||
if !file.postinstall {
|
|
||||||
file.create(&output_dir)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
install_packages(&config, &output_dir, cookbook)?;
|
|
||||||
|
|
||||||
for file in &config.files {
|
|
||||||
if file.postinstall {
|
|
||||||
file.create(&output_dir)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut passwd = String::new();
|
|
||||||
let mut shadow = String::new();
|
|
||||||
let mut next_uid = 1000;
|
|
||||||
let mut next_gid = 1000;
|
|
||||||
|
|
||||||
let mut groups = vec![];
|
|
||||||
|
|
||||||
for (username, user) in config.users {
|
|
||||||
// plaintext
|
|
||||||
let password = if let Some(password) = user.password {
|
|
||||||
password
|
|
||||||
} else if config.general.prompt.unwrap_or(true) {
|
|
||||||
prompt_password(
|
|
||||||
&format!("{}: enter password: ", username),
|
|
||||||
&format!("{}: confirm password: ", username),
|
|
||||||
)?
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let uid = user.uid.unwrap_or(next_uid);
|
|
||||||
|
|
||||||
if uid >= next_uid {
|
|
||||||
next_uid = uid + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gid = user.gid.unwrap_or(next_gid);
|
|
||||||
|
|
||||||
if gid >= next_gid {
|
|
||||||
next_gid = gid + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = user.name.unwrap_or(username.clone());
|
|
||||||
let home = user.home.unwrap_or(format!("/home/{}", username));
|
|
||||||
let shell = user.shell.unwrap_or("/bin/ion".into());
|
|
||||||
|
|
||||||
println!("Adding user {username}:");
|
|
||||||
if password.is_empty() {
|
|
||||||
println!("\tPassword: unset");
|
|
||||||
} else {
|
|
||||||
println!("\tPassword: set");
|
|
||||||
}
|
|
||||||
println!("\tUID: {uid}");
|
|
||||||
println!("\tGID: {gid}");
|
|
||||||
println!("\tName: {name}");
|
|
||||||
println!("\tHome: {home}");
|
|
||||||
println!("\tShell: {shell}");
|
|
||||||
|
|
||||||
FileConfig::new_directory(home.clone())
|
|
||||||
.with_recursive_mod(0o700, uid, gid)
|
|
||||||
.create(&output_dir)?;
|
|
||||||
|
|
||||||
if uid >= 1000 {
|
|
||||||
prepare_user_home(&output_dir, uid, gid, &home)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let password = hash_password(&password)?;
|
|
||||||
|
|
||||||
passwd.push_str(&format!("{username};{uid};{gid};{name};{home};{shell}\n",));
|
|
||||||
shadow.push_str(&format!("{username};{password}\n"));
|
|
||||||
groups.push((username.clone(), gid, vec![username]));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (group, group_config) in config.groups {
|
|
||||||
// FIXME this assumes there is no overlap between auto-created groups for users
|
|
||||||
// and explicitly specified groups.
|
|
||||||
let gid = group_config.gid.unwrap_or(next_gid);
|
|
||||||
|
|
||||||
if gid >= next_gid {
|
|
||||||
next_gid = gid + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups.push((group, gid, group_config.members));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !passwd.is_empty() {
|
|
||||||
FileConfig::new_file("/etc/passwd".to_string(), passwd).create(&output_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !shadow.is_empty() {
|
|
||||||
FileConfig::new_file("/etc/shadow".to_string(), shadow)
|
|
||||||
.with_mod(0o0600, 0, 0)
|
|
||||||
.create(&output_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !groups.is_empty() {
|
|
||||||
let mut groups_data = String::new();
|
|
||||||
|
|
||||||
for (name, gid, members) in groups {
|
|
||||||
use std::fmt::Write;
|
|
||||||
writeln!(groups_data, "{name};x;{gid};{}", members.join(","))?;
|
|
||||||
|
|
||||||
println!("Adding group {name}:");
|
|
||||||
println!("\tGID: {gid}");
|
|
||||||
println!("\tMembers: {}", members.join(", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
FileConfig::new_file("/etc/group".to_string(), groups_data)
|
|
||||||
.with_mod(0o0600, 0, 0)
|
|
||||||
.create(&output_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_user_home(
|
|
||||||
output_dir: &PathBuf,
|
|
||||||
uid: u32,
|
|
||||||
gid: u32,
|
|
||||||
home: &String,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
for xdg_folder in &[
|
|
||||||
"Desktop",
|
|
||||||
"Documents",
|
|
||||||
"Downloads",
|
|
||||||
"Music",
|
|
||||||
"Pictures",
|
|
||||||
"Public",
|
|
||||||
"Templates",
|
|
||||||
"Videos",
|
|
||||||
".config",
|
|
||||||
".local",
|
|
||||||
".local/share",
|
|
||||||
".local/share/Trash",
|
|
||||||
".local/share/Trash/info",
|
|
||||||
] {
|
|
||||||
FileConfig::new_directory(format!("{}/{}", home, xdg_folder))
|
|
||||||
.with_mod(0o0700, uid, gid)
|
|
||||||
.create(output_dir)?;
|
|
||||||
}
|
|
||||||
FileConfig::new_file(
|
|
||||||
format!("{}/.config/user-dirs.dirs", home),
|
|
||||||
r#"# Produced by redox installer
|
|
||||||
XDG_DESKTOP_DIR="$HOME/Desktop"
|
|
||||||
XDG_DOCUMENTS_DIR="$HOME/Documents"
|
|
||||||
XDG_DOWNLOAD_DIR="$HOME/Downloads"
|
|
||||||
XDG_MUSIC_DIR="$HOME/Music"
|
|
||||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
|
||||||
XDG_PUBLICSHARE_DIR="$HOME/Public"
|
|
||||||
XDG_TEMPLATES_DIR="$HOME/Templates"
|
|
||||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
|
||||||
"#
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.with_mod(0o0600, uid, gid)
|
|
||||||
.create(output_dir)?;
|
|
||||||
|
|
||||||
let skel_dir = output_dir.join("etc/skel");
|
|
||||||
if skel_dir.is_dir() {
|
|
||||||
copy_dir_all(&skel_dir, home.clone(), output_dir, uid, gid)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_dir_all(
|
|
||||||
src: impl AsRef<Path>,
|
|
||||||
dst: String,
|
|
||||||
output_dir: &Path,
|
|
||||||
uid: u32,
|
|
||||||
gid: u32,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if !Path::new(dst.as_str()).is_dir() {
|
|
||||||
FileConfig::new_directory(dst.clone())
|
|
||||||
.with_mod(0o0700, uid, gid)
|
|
||||||
.create(&output_dir)?;
|
|
||||||
}
|
|
||||||
for entry in fs::read_dir(src)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let file_type = entry.file_type()?;
|
|
||||||
let dst_path = format!("{}/{}", dst, entry.file_name().display());
|
|
||||||
if file_type.is_dir() {
|
|
||||||
copy_dir_all(entry.path(), dst_path, output_dir, uid, gid)?;
|
|
||||||
} else if file_type.is_file() {
|
|
||||||
FileConfig::new_file(
|
|
||||||
dst_path,
|
|
||||||
fs::read_to_string(entry.path())
|
|
||||||
.with_context(|| format!("Reading {}", entry.path().display()))?,
|
|
||||||
)
|
|
||||||
.with_mod(0o0600, uid, gid)
|
|
||||||
.create(&output_dir)?;
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F) -> Result<T>
|
|
||||||
where
|
|
||||||
D: Disk + Send + 'static,
|
|
||||||
F: FnOnce(FileSystem<D>) -> Result<T>,
|
|
||||||
{
|
|
||||||
let ctime = SystemTime::now().duration_since(UNIX_EPOCH)?;
|
|
||||||
let fs = FileSystem::create(disk, password_opt, ctime.as_secs(), ctime.subsec_nanos())
|
|
||||||
.map_err(syscall_error)?;
|
|
||||||
callback(fs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decide_mount_path(mount_path: Option<&Path>) -> PathBuf {
|
|
||||||
let mount_path = mount_path.map(|p| p.to_path_buf()).unwrap_or_else(|| {
|
|
||||||
PathBuf::from(if cfg!(target_os = "redox") {
|
|
||||||
format!("file.redox_installer_{}", process::id())
|
|
||||||
} else {
|
|
||||||
format!("/tmp/redox_installer_{}", process::id())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
mount_path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_redoxfs_mount<D, T, F>(
|
|
||||||
fs: FileSystem<D>,
|
|
||||||
mount_path: Option<&Path>,
|
|
||||||
callback: F,
|
|
||||||
) -> Result<T>
|
|
||||||
where
|
|
||||||
D: Disk + Send + 'static,
|
|
||||||
F: FnOnce(&Path) -> Result<T>,
|
|
||||||
{
|
|
||||||
let mount_path = decide_mount_path(mount_path);
|
|
||||||
|
|
||||||
if cfg!(not(target_os = "redox")) && !mount_path.exists() {
|
|
||||||
fs::create_dir(&mount_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let join_handle = {
|
|
||||||
let mount_path = mount_path.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let res = redoxfs::mount(fs, &mount_path, |real_path| {
|
|
||||||
tx.send(Ok(real_path.to_owned())).unwrap();
|
|
||||||
});
|
|
||||||
match res {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(err) => {
|
|
||||||
tx.send(Err(err)).unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = match rx.recv() {
|
|
||||||
Ok(ok) => match ok {
|
|
||||||
Ok(real_path) => callback(&real_path),
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::NotConnected,
|
|
||||||
"redoxfs thread did not send a result",
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unmount_path(&mount_path.as_os_str().to_str().unwrap())?;
|
|
||||||
|
|
||||||
join_handle.join().unwrap();
|
|
||||||
|
|
||||||
if cfg!(not(target_os = "redox")) {
|
|
||||||
fs::remove_dir_all(&mount_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_redoxfs_ar<D, T, F>(
|
|
||||||
mut fs: FileSystem<D>,
|
|
||||||
mount_path: Option<&Path>,
|
|
||||||
callback: F,
|
|
||||||
) -> Result<T>
|
|
||||||
where
|
|
||||||
D: Disk + Send + 'static,
|
|
||||||
F: FnOnce(&Path) -> Result<T>,
|
|
||||||
{
|
|
||||||
let mount_path = decide_mount_path(mount_path);
|
|
||||||
|
|
||||||
let res = callback(Path::new(&mount_path));
|
|
||||||
|
|
||||||
if res.is_ok() {
|
|
||||||
let _end_block = fs
|
|
||||||
.tx(|tx| {
|
|
||||||
// Archive_at root node
|
|
||||||
redoxfs::archive_at(tx, Path::new(&mount_path), redoxfs::TreePtr::root())
|
|
||||||
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
|
|
||||||
|
|
||||||
// Squash alloc log
|
|
||||||
tx.sync(true)?;
|
|
||||||
|
|
||||||
let end_block = tx.header.size() / BLOCK_SIZE;
|
|
||||||
/* TODO: Cut off any free blocks at the end of the filesystem
|
|
||||||
let mut end_changed = true;
|
|
||||||
while end_changed {
|
|
||||||
end_changed = false;
|
|
||||||
|
|
||||||
let allocator = fs.allocator();
|
|
||||||
let levels = allocator.levels();
|
|
||||||
for level in 0..levels.len() {
|
|
||||||
let level_size = 1 << level;
|
|
||||||
for &block in levels[level].iter() {
|
|
||||||
if block < end_block && block + level_size >= end_block {
|
|
||||||
end_block = block;
|
|
||||||
end_changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Update header
|
|
||||||
tx.header.size = (end_block * BLOCK_SIZE).into();
|
|
||||||
tx.header_changed = true;
|
|
||||||
tx.sync(false)?;
|
|
||||||
|
|
||||||
Ok(end_block)
|
|
||||||
})
|
|
||||||
.map_err(syscall_error)?;
|
|
||||||
|
|
||||||
// let size = (fs.block + end_block) * BLOCK_SIZE;
|
|
||||||
// fs.disk.file.set_len(size)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::remove_dir_all(&mount_path)?;
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_bootloaders(
|
|
||||||
config: &Config,
|
|
||||||
cookbook: Option<&str>,
|
|
||||||
live: bool,
|
|
||||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
|
||||||
let bootloader_dir =
|
|
||||||
PathBuf::from(format!("/tmp/redox_installer_bootloader_{}", process::id()));
|
|
||||||
|
|
||||||
if bootloader_dir.exists() {
|
|
||||||
fs::remove_dir_all(&bootloader_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::create_dir(&bootloader_dir)?;
|
|
||||||
|
|
||||||
let mut bootloader_config = Config::bootloader_config();
|
|
||||||
bootloader_config.general = config.general.clone();
|
|
||||||
install_packages(&bootloader_config, &bootloader_dir, cookbook)?;
|
|
||||||
|
|
||||||
let boot_dir = bootloader_dir.join("usr/lib/boot");
|
|
||||||
let bios_path = boot_dir.join(if live {
|
|
||||||
"bootloader-live.bios"
|
|
||||||
} else {
|
|
||||||
"bootloader.bios"
|
|
||||||
});
|
|
||||||
let efi_path = boot_dir.join(if live {
|
|
||||||
"bootloader-live.efi"
|
|
||||||
} else {
|
|
||||||
"bootloader.efi"
|
|
||||||
});
|
|
||||||
|
|
||||||
let bios_data = if bios_path.exists() {
|
|
||||||
fs::read(bios_path)?
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
let efi_data = if efi_path.exists() {
|
|
||||||
fs::read(efi_path)?
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
fs::remove_dir_all(&bootloader_dir)?;
|
|
||||||
|
|
||||||
Ok((bios_data, efi_data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: make bootloaders use Option, dynamically create BIOS and EFI partitions
|
|
||||||
pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback: F) -> Result<T>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
F: FnOnce(FileSystem<DiskIo<fscommon::StreamSlice<DiskWrapper>>>) -> Result<T>,
|
|
||||||
{
|
|
||||||
let target = get_target();
|
|
||||||
|
|
||||||
let bootloader_efi_name = match target.as_str() {
|
|
||||||
"aarch64-unknown-redox" => "BOOTAA64.EFI",
|
|
||||||
"i586-unknown-redox" | "i686-unknown-redox" => "BOOTIA32.EFI",
|
|
||||||
"x86_64-unknown-redox" => "BOOTX64.EFI",
|
|
||||||
"riscv64gc-unknown-redox" => "BOOTRISCV64.EFI",
|
|
||||||
_ => {
|
|
||||||
bail!("target '{target}' not supported");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Open disk and read metadata
|
|
||||||
eprintln!("Opening disk {}", disk_path.as_ref().display());
|
|
||||||
let mut disk_file = DiskWrapper::open(disk_path.as_ref())?;
|
|
||||||
let disk_size = disk_file.size();
|
|
||||||
let block_size = disk_file.block_size() as u64;
|
|
||||||
|
|
||||||
if disk_option.skip_partitions {
|
|
||||||
return with_redoxfs(
|
|
||||||
DiskIo(fscommon::StreamSlice::new(
|
|
||||||
disk_file,
|
|
||||||
0,
|
|
||||||
disk_size.next_multiple_of(block_size),
|
|
||||||
)?),
|
|
||||||
disk_option.password_opt,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let gpt_block_size = match block_size {
|
|
||||||
512 => gpt::disk::LogicalBlockSize::Lb512,
|
|
||||||
_ => {
|
|
||||||
// TODO: support (and test) other block sizes
|
|
||||||
bail!("block size {block_size} not supported");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate partition offsets
|
|
||||||
let gpt_reserved = 34 * 512; // GPT always reserves 34 512-byte sectors
|
|
||||||
let mibi = 1024 * 1024;
|
|
||||||
|
|
||||||
// First megabyte of the disk is reserved for BIOS partition, wich includes GPT tables
|
|
||||||
let bios_start = gpt_reserved / block_size;
|
|
||||||
let bios_end = (mibi / block_size) - 1;
|
|
||||||
|
|
||||||
// Second megabyte of the disk is reserved for EFI partition
|
|
||||||
let efi_start = bios_end + 1;
|
|
||||||
let efi_size = if let Some(size) = disk_option.efi_partition_size {
|
|
||||||
size as u64
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
let efi_end = efi_start + (efi_size * mibi / block_size) - 1;
|
|
||||||
|
|
||||||
// The rest of the disk is RedoxFS, reserving the GPT table mirror at the end of disk
|
|
||||||
let redoxfs_start = efi_end + 1;
|
|
||||||
let redoxfs_end = ((((disk_size - gpt_reserved) / mibi) * mibi) / block_size) - 1;
|
|
||||||
|
|
||||||
// Format and install BIOS partition
|
|
||||||
{
|
|
||||||
// Write BIOS bootloader to disk
|
|
||||||
eprintln!(
|
|
||||||
"Write bootloader with size {:#x}",
|
|
||||||
disk_option.bootloader_bios.len()
|
|
||||||
);
|
|
||||||
disk_file.seek(SeekFrom::Start(0))?;
|
|
||||||
disk_file.write_all(&disk_option.bootloader_bios)?;
|
|
||||||
|
|
||||||
// Replace MBR tables with protective MBR
|
|
||||||
// TODO: div_ceil
|
|
||||||
let mbr_blocks = ((disk_size + block_size - 1) / block_size) - 1;
|
|
||||||
eprintln!("Writing protective MBR with disk blocks {mbr_blocks:#x}");
|
|
||||||
gpt::mbr::ProtectiveMBR::with_lb_size(mbr_blocks as u32)
|
|
||||||
.update_conservative(&mut disk_file)?;
|
|
||||||
|
|
||||||
// Open disk, mark it as not initialized
|
|
||||||
let mut gpt_disk = gpt::GptConfig::new()
|
|
||||||
.initialized(false)
|
|
||||||
.writable(true)
|
|
||||||
.logical_block_size(gpt_block_size)
|
|
||||||
.create_from_device(Box::new(&mut disk_file), None)?;
|
|
||||||
|
|
||||||
// Add BIOS boot partition
|
|
||||||
let mut partitions = BTreeMap::new();
|
|
||||||
let mut partition_id = 1;
|
|
||||||
partitions.insert(
|
|
||||||
partition_id,
|
|
||||||
gpt::partition::Partition {
|
|
||||||
part_type_guid: gpt::partition_types::BIOS,
|
|
||||||
part_guid: uuid::Uuid::new_v4(),
|
|
||||||
first_lba: bios_start,
|
|
||||||
last_lba: bios_end,
|
|
||||||
flags: 0, // TODO
|
|
||||||
name: "BIOS".to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
partition_id += 1;
|
|
||||||
|
|
||||||
// Add EFI boot partition
|
|
||||||
partitions.insert(
|
|
||||||
partition_id,
|
|
||||||
gpt::partition::Partition {
|
|
||||||
part_type_guid: gpt::partition_types::EFI,
|
|
||||||
part_guid: uuid::Uuid::new_v4(),
|
|
||||||
first_lba: efi_start,
|
|
||||||
last_lba: efi_end,
|
|
||||||
flags: 0, // TODO
|
|
||||||
name: "EFI".to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
partition_id += 1;
|
|
||||||
|
|
||||||
// Add RedoxFS partition
|
|
||||||
partitions.insert(
|
|
||||||
partition_id,
|
|
||||||
gpt::partition::Partition {
|
|
||||||
//TODO: Use REDOX_REDOXFS type (needs GPT crate changes)
|
|
||||||
part_type_guid: gpt::partition_types::LINUX_FS,
|
|
||||||
part_guid: uuid::Uuid::new_v4(),
|
|
||||||
first_lba: redoxfs_start,
|
|
||||||
last_lba: redoxfs_end,
|
|
||||||
flags: 0,
|
|
||||||
name: "REDOX".to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
eprintln!("Writing GPT tables: {partitions:#?}");
|
|
||||||
|
|
||||||
// Initialize GPT table
|
|
||||||
gpt_disk.update_partitions(partitions)?;
|
|
||||||
|
|
||||||
// Write partition layout, returning disk file
|
|
||||||
gpt_disk.write()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format and install EFI partition
|
|
||||||
{
|
|
||||||
let disk_efi_start = efi_start * block_size;
|
|
||||||
let disk_efi_end = (efi_end + 1) * block_size;
|
|
||||||
let mut disk_efi =
|
|
||||||
fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?;
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"Formatting EFI partition with size {:#x}",
|
|
||||||
disk_efi_end - disk_efi_start
|
|
||||||
);
|
|
||||||
fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?;
|
|
||||||
|
|
||||||
eprintln!("Opening EFI partition");
|
|
||||||
let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?;
|
|
||||||
|
|
||||||
eprintln!("Creating EFI directory");
|
|
||||||
let root_dir = fs.root_dir();
|
|
||||||
root_dir.create_dir("EFI")?;
|
|
||||||
|
|
||||||
eprintln!("Creating EFI/BOOT directory");
|
|
||||||
let efi_dir = root_dir.open_dir("EFI")?;
|
|
||||||
efi_dir.create_dir("BOOT")?;
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"Writing EFI/BOOT/{} file with size {:#x}",
|
|
||||||
bootloader_efi_name,
|
|
||||||
disk_option.bootloader_efi.len()
|
|
||||||
);
|
|
||||||
let boot_dir = efi_dir.open_dir("BOOT")?;
|
|
||||||
let mut file = boot_dir.create_file(bootloader_efi_name)?;
|
|
||||||
file.truncate()?;
|
|
||||||
file.write_all(&disk_option.bootloader_efi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format and install RedoxFS partition
|
|
||||||
eprintln!(
|
|
||||||
"Installing to RedoxFS partition with size {:#x}",
|
|
||||||
(redoxfs_end - redoxfs_start) * block_size
|
|
||||||
);
|
|
||||||
let disk_redoxfs = DiskIo(fscommon::StreamSlice::new(
|
|
||||||
disk_file,
|
|
||||||
redoxfs_start * block_size,
|
|
||||||
(redoxfs_end + 1) * block_size,
|
|
||||||
)?);
|
|
||||||
with_redoxfs(disk_redoxfs, disk_option.password_opt, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
|
||||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
|
||||||
_fs: &mut redoxfs::FileSystem<D>,
|
|
||||||
_progress: F,
|
|
||||||
) -> Result<bool> {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try fast install using live disk memory
|
|
||||||
#[cfg(target_os = "redox")]
|
|
||||||
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
|
|
||||||
fs: &mut redoxfs::FileSystem<D>,
|
|
||||||
mut progress: F,
|
|
||||||
) -> Result<bool> {
|
|
||||||
use libredox::{call::MmapArgs, flag};
|
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
use syscall::PAGE_SIZE;
|
|
||||||
|
|
||||||
let phys = env::var("DISK_LIVE_ADDR")
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let size = env::var("DISK_LIVE_SIZE")
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| usize::from_str_radix(&x, 16).ok())
|
|
||||||
.unwrap_or(0);
|
|
||||||
if phys == 0 || size == 0 {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = (phys / PAGE_SIZE) * PAGE_SIZE;
|
|
||||||
let end = phys
|
|
||||||
.checked_add(size)
|
|
||||||
.context("phys + size overflow")?
|
|
||||||
.next_multiple_of(PAGE_SIZE);
|
|
||||||
let size = end - start;
|
|
||||||
|
|
||||||
let original = unsafe {
|
|
||||||
//TODO: unmap this memory
|
|
||||||
let file = fs::File::open("/scheme/memory/physical")?;
|
|
||||||
let base = libredox::call::mmap(MmapArgs {
|
|
||||||
fd: file.as_raw_fd() as usize,
|
|
||||||
addr: core::ptr::null_mut(),
|
|
||||||
offset: start as u64,
|
|
||||||
length: size,
|
|
||||||
prot: flag::PROT_READ,
|
|
||||||
flags: flag::MAP_SHARED,
|
|
||||||
})
|
|
||||||
.map_err(|err| anyhow::anyhow!("failed to mmap livedisk: {}", err))?;
|
|
||||||
|
|
||||||
std::slice::from_raw_parts(base as *const u8, size)
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DiskLive {
|
|
||||||
original: &'static [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl redoxfs::Disk for DiskLive {
|
|
||||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
|
||||||
let offset = (block * redoxfs::BLOCK_SIZE) as usize;
|
|
||||||
if offset + buffer.len() > self.original.len() {
|
|
||||||
return Err(syscall::Error::new(syscall::EINVAL));
|
|
||||||
}
|
|
||||||
buffer.copy_from_slice(&self.original[offset..offset + buffer.len()]);
|
|
||||||
Ok(buffer.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
|
|
||||||
Err(syscall::Error::new(syscall::EINVAL))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&mut self) -> syscall::Result<u64> {
|
|
||||||
Ok(self.original.len() as u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut fs_old = redoxfs::FileSystem::open(DiskLive { original }, None, None, false)?;
|
|
||||||
let size_old = fs_old.header.size();
|
|
||||||
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
|
|
||||||
let used_old = size_old - free_old;
|
|
||||||
redoxfs::clone(&mut fs_old, fs, move |used| {
|
|
||||||
progress(used, used_old);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_inner(config: Config, output: &Path) -> Result<()> {
|
|
||||||
println!("Installing to {}:\n{}", output.display(), config);
|
|
||||||
let cookbook = config.general.cookbook.clone();
|
|
||||||
let cookbook = cookbook.as_ref().map(|p| p.as_str());
|
|
||||||
if output.is_dir() {
|
|
||||||
install_dir(config, output, cookbook)
|
|
||||||
} else {
|
|
||||||
if !output.is_file() {
|
|
||||||
let fs_size = config.general.filesystem_size.unwrap_or(0) as u64;
|
|
||||||
// arbitrary size approximately fit just for initfs
|
|
||||||
if fs_size < 32 {
|
|
||||||
bail!("Refusing to create image disk less than 32 MB");
|
|
||||||
}
|
|
||||||
eprintln!(
|
|
||||||
"Creating a new file to {} with size {} MB",
|
|
||||||
output.display(),
|
|
||||||
fs_size
|
|
||||||
);
|
|
||||||
let file = fs::File::create(output)?;
|
|
||||||
file.set_len(fs_size * 1024 * 1024)?;
|
|
||||||
}
|
|
||||||
let live = config.general.live_disk.unwrap_or(false);
|
|
||||||
let password_opt = config.general.encrypt_disk.clone();
|
|
||||||
let password_opt = password_opt.as_ref().map(|p| p.as_bytes());
|
|
||||||
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?;
|
|
||||||
if let Some(write_bootloader) = &config.general.write_bootloader {
|
|
||||||
std::fs::write(write_bootloader, &bootloader_efi)?;
|
|
||||||
}
|
|
||||||
let disk_option = DiskOption {
|
|
||||||
bootloader_bios: &bootloader_bios,
|
|
||||||
bootloader_efi: &bootloader_efi,
|
|
||||||
password_opt: password_opt,
|
|
||||||
efi_partition_size: config.general.efi_partition_size,
|
|
||||||
skip_partitions: config.general.skip_partitions.unwrap_or(false),
|
|
||||||
};
|
|
||||||
with_whole_disk(output, &disk_option, move |fs| {
|
|
||||||
if config.general.no_mount.unwrap_or(false) {
|
|
||||||
with_redoxfs_ar(fs, None, move |mount_path| {
|
|
||||||
install_dir(config, mount_path, cookbook)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
with_redoxfs_mount(fs, None, move |mount_path| {
|
|
||||||
install_dir(config, mount_path, cookbook)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Install RedoxFS into a new disk file, or a sysroot directory.
|
|
||||||
/// This function assumes all interactive prompts resolved by the caller.
|
|
||||||
pub fn install(config: Config, output: impl AsRef<Path>) -> Result<()> {
|
|
||||||
install_inner(config, output.as_ref())
|
|
||||||
}
|
|
||||||
+1141
-12
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
IMAGE=test.bin
|
|
||||||
|
|
||||||
QEMU_ARGS=(
|
|
||||||
-cpu max
|
|
||||||
-machine q35
|
|
||||||
-m 2048
|
|
||||||
-smp 4
|
|
||||||
-serial mon:stdio
|
|
||||||
-netdev user,id=net0
|
|
||||||
-device e1000,netdev=net0
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ -e /dev/kvm ]
|
|
||||||
then
|
|
||||||
QEMU_ARGS+=(-accel kvm)
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
cargo build --release
|
|
||||||
|
|
||||||
rm -f "${IMAGE}"
|
|
||||||
fallocate -l 1GiB "${IMAGE}"
|
|
||||||
|
|
||||||
target/release/redox_installer -c res/test.toml "${IMAGE}"
|
|
||||||
|
|
||||||
qemu-system-x86_64 "${QEMU_ARGS[@]}" -drive "file=${IMAGE},format=raw"
|
|
||||||
Reference in New Issue
Block a user