Skip to content

Commit

Permalink
feat: enter command for integrated user setup + typing + compose refa…
Browse files Browse the repository at this point in the history
…ctoring + version command for nuspawn meta info + reusing initialization code in envire repo
  • Loading branch information
tulilirockz committed Jun 1, 2024
1 parent 25f2ae7 commit 2f91959
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 105 deletions.
17 changes: 12 additions & 5 deletions example/config/distrobox-like.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
[Network]
VirtualEthernet=no

[Exec]
Boot=yes
Environment=DISPLAY=:0
Environment=TERM=xterm-256color
Environment=WAYLAND_DISPLAY=wayland-1
Environment=XDG_RUNTIME_DIR=/run/user/1000
SystemCallFilter=add_key keyctl bpf

[Files]
BindUser=your_user_here
Bind=/home:/home
TemporaryFileSystem=/tmp
Environment=DISPLAY=:0
Boot=yes
PrivateUsers=yes
Bind=/dev/fuse:/dev/fuse
BindReadOnly=/run/user:/run/user
Bind=/dev/dri:/dev/dri
BindUser=tulili
39 changes: 21 additions & 18 deletions src/lib/compose.nu
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use meta.nu [NAME, NSPAWNHUB_STORAGE_ROOT, MACHINE_STORAGE_PATH, MACHINE_CONFIG_PATH]
use logger.nu *
use std assert

const CONFIG_EXTENSION = "nspawn"
use machine_manager.nu [CONFIG_EXTENSION, run_container]

# Compose Nspawn machines from YAML
export def "main compose" [] {
Expand All @@ -11,15 +10,15 @@ export def "main compose" [] {

# Create new machines from YAML manifest
export def --env "main compose create" [
--storage-root: string = $MACHINE_STORAGE_PATH # Storage root for machines
--config-root: string = $MACHINE_CONFIG_PATH # Configuration path for machines
--nspawnhub-url: path = $NSPAWNHUB_STORAGE_ROOT # Fallback NspawnHub URL for images
--storage-root: path = $MACHINE_STORAGE_PATH # Storage root for machines
--config-root: path = $MACHINE_CONFIG_PATH # Configuration path for machines
--override-config = true # Whether to override an existing machine configuration if it is already configured
--override # Override existing machines
--config: string # Fallback configuration for all images
--user: string = "root" # Default user to operate on machine
--verify (-v): string = "checksum" # Fallback mode to verify images once pulled
--nspawnhub-url: string = $NSPAWNHUB_STORAGE_ROOT # Fallback NspawnHub URL for images
manifest: string # Manifest to be used
manifest: path # Manifest to be used
] {
let manifest_data = (open $manifest)
if $manifest_data.version != "0.5" {
Expand All @@ -37,7 +36,7 @@ export def --env "main compose create" [
assert ($manifest_data.machines != null)
for machine in $manifest_data.machines {
try {
if (($"($storage_root)/($machine.name)" | path exists) or ($"($storage_root)/($machine.name).raw" | path exists)) and (not $override) {
if (not $override) and (($"($storage_root)/($machine.name)" | path exists) or ($"($storage_root)/($machine.name).raw" | path exists)) {
logger warning $"[($machine.name)] Machine is already initialized, skipping"
continue
}
Expand All @@ -48,7 +47,19 @@ export def --env "main compose create" [

assert ($machine.name != null) "Your machine must have a name"

main init --nspawnhub-url=($machine.nspawnhub_url? | default $nspawnhub_url) --verify=($machine.verify? | default $verify) --name=($machine.name) --config=($machine.config? | default $config) --config-root=($config_root) --storage-root=($storage_root) --from-url=($machine.from-url?) --override=($override) $machine.image? $machine.tag?
(main
init
--nspawnhub-url=($machine.nspawnhub_url? | default $nspawnhub_url)
--verify=($machine.verify? | default $verify)
--from-url=($machine.from-url?)
--nspawn=(not $machine.systemd? | default true)
--name=($machine.name) --config=($machine.config? | default $config)
--config-root=($config_root)
--storage-root=($storage_root)
--override=($override)
$machine.image?
$machine.tag?
)

let machine_config_path = $"($config_root)/($machine.name).($CONFIG_EXTENSION)"
if $machine.config? != null {
Expand All @@ -71,16 +82,8 @@ export def --env "main compose create" [
continue
}
}
if $machine.init_commands? != null {
if ($machine.systemd? | default false) {
run-external 'systemd-nspawn' '-M' $'($machine.name)' '/usr/bin/env' $"($machine.env? | default "PATH=/usr/bin:/usr/local/bin:/bin" | str join ' ')" '/bin/sh' '-c' $"($machine.init_commands | str join ' ; ')"
continue
}

run-external 'machinectl' 'start' $'($machine.name)'
sleep 2sec
run-external 'machinectl' 'shell' $"($user)@($machine.name)" '/usr/bin/env' $"($machine.env? | default "PATH=/usr/bin:/usr/local/bin:/bin" | str join ' ')" '/bin/sh' '-c' $"($machine.init_commands | str join ' ; ')"
run-external 'machinectl' 'stop' $'($machine.name)'
if $machine.init_commands? != null {
run_container --nspawn=(not $machine.systemd? | default true) $machine.name $"($machine.init_commands | str join ' ; ')"
}
if $machine.properties? != null {
for property in $machine.properties {
Expand Down
47 changes: 47 additions & 0 deletions src/lib/enter.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std assert
use machine_manager.nu [machinectl run_container]

# Enter and setup an nspawn container with your current user
# Requires your container to have a recent version of systemd-userdb if you are binding your current user to the machine
export def --env "main enter" [
--machinectl (-m) # Use machinectl for operations instead of machinectl
--shadow = true # Copy your user hashed password from /etc/shadow and put it inside the container
--root-user: string = "root" # User with root privileges in the container
--environment (-e): list<string> # Test
--setup-no-bind = false # Sets up the container for usage without binding user
--no-bind = false # Use this if you are having issue with user binding
--bind-dirs = "/home:/home" # Comma separated list of directories bound to the container (e.g.: /home/developer:/opt/dev./home/tulili:/tmp/hosthome)
--user: string # User that will be binded to the container
machine: string # Name of the machine to be logged into
...args: list<string> # Extra arguments to pass to the backend
] {
let user = (if $user != null { $user } else { ($env.USER? | default root) })

if not $machinectl {
try {
machinectl stop $machine | ignore
}
(systemd-run
--uid=0
--gid=0
-t
-q
--
'systemd-nspawn'
'-b'
'-M'
$'($machine)'
'--bind=/home:/home'
'--bind=/run/user:/run/user'
'--set-credential=firstboot.locale:C.UTF-8'
'--bind=/dev/dri'
'--bind=/dev/shm'
$'--setenv=DISPLAY=($env.DISPLAY? | default ":0")'
$"--setenv=WAYLAND_DISPLAY=($env.WAYLAND_DISPLAY? | default "wayland-1")"
(if not $no_bind { $"--bind-user=($user)" })
(if not $no_bind {"-U"})
)
return
}
machinectl shell $"($user)@($machine)" # Should be pre-configured by init or compose
}
88 changes: 44 additions & 44 deletions src/lib/init.nu
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use logger.nu *
use meta.nu [NSPAWNHUB_KEY_LOCATION, NSPAWNHUB_STORAGE_ROOT, MACHINE_STORAGE_PATH, MACHINE_CONFIG_PATH]
use machine_manager.nu [machinectl, run_container]
use std assert
use config.nu ["main config apply", "main config"]
use verify.nu [gpg]

# Import tar/raw images to machinectl from nspawnhub or any other registry.
export def --env "main init" [
--verify (-v): string = "checksum" # The type of verification ran on the images
--name (-n): string # Name of the image imported to machinectl
--config (-c): string # Path for the nspawn config to be copied to /var/lib/machines
--nspawnhub-url: path = $NSPAWNHUB_STORAGE_ROOT # URL for Nspawnhub's storage root
--storage-root: path = $MACHINE_STORAGE_PATH # Local storage path for Nspawn machines
--config-root: path = $MACHINE_CONFIG_PATH # Local storage path for Nspawn machines
--verify (-v): string = "checksum" # The type of verification ran on the images ("no", "checksum", "gpg")
--name (-n): string # Name of the machine to be called
--config (-c): path # Path for the nspawn config to be applied
--override (-o) # Overrides the existing machine in storage
--override-config = true # Overrides the existing configuration for the container
--type (-t): string = "tar" # Type of machine (Raw or Tarball)
--nspawnhub-url: string = $NSPAWNHUB_STORAGE_ROOT # URL for Nspawnhub's storage root
--storage-root: string = $MACHINE_STORAGE_PATH # Local storage path for Nspawn machines
--config-root: string = $MACHINE_CONFIG_PATH # Local storage path for Nspawn machines
--from-url (-u): string # Fetch image from URL instead of NspawnHub
--pull-only (-p): string # Just pull the image without setting up anything
--nspawn (-n) # Use nspawn as the backend for operations instead of machinectl (necessary for systemd-less images)
--yes (-y) # Skip any input questions and just confirm them
image?: string = "debian"
tag?: string = "sid"
] {
Expand All @@ -22,88 +28,82 @@ export def --env "main init" [

if ($verify == "gpg") and (not ($nspawnhub_gpg_path | path exists)) {
logger error "Could not find nspawnhub's GPG keys"
let yesno = (input $"(ansi blue_bold)Do you wish to fetch them? [y/n]: (ansi reset)")
if not $yes {
let yesno = (input $"(ansi blue_bold)Do you wish to fetch them? [y/n]: (ansi reset)")

match $yesno {
Y|Yes|yes|y => { }
_ => { return }
match $yesno {
Y|Yes|yes|y => { }
_ => { return }
}
}

logger info "Fetching Nspawnhub keys..."
mkdir ($nspawnhub_gpg_path | path dirname)
mkdir $"($env.XDG_DATA_HOME? | default $"($env.HOME)/.local/share")/gnupg" # prevent gnupg from being annoying
run-external 'gpg' '--no-default-keyring' $"--keyring=($nspawnhub_gpg_path)" '--fingerprint'
gpg --no-default-keyring --keyring=($nspawnhub_gpg_path) --fingerprint

mkdir $nuspawn_cache
let tfile = (mktemp -p $nuspawn_cache --suffix .gpg masterkey.nspawn.org.XXXXXXX)
http get $NSPAWNHUB_KEY_LOCATION | save -f $tfile
run-external 'gpg' '--no-default-keyring' $"--keyring=($nspawnhub_gpg_path)" '--import' $"($tfile)"
gpg --no-default-keyring --keyring=($nspawnhub_gpg_path) --import $"($tfile)"
}

let full_image_name = $"($nspawnhub_url)/($image)/($tag)/($type)/image.($type).xz"
let nspawnhub_image_url = $"($nspawnhub_url)/($image)/($tag)/($type)/image.($type).xz"
mut output_image = $"($image)-($tag)-($type)"
if $name != null {
$output_image = $name
}


try {
if $from_url == null {
http head $full_image_name | ignore
} else {
http head $from_url | ignore
}
http head (if $from_url != null { $from_url } else { $nspawnhub_image_url }) | ignore
} catch {
logger error "Failure finding remote image, check if image is valid"
return
}

if ((run-external 'machinectl' 'show-image' $output_image | complete | get exit_code) != 1) {
if ((machinectl show-image $output_image | complete | get exit_code) != 1) {
if not $override {
logger error "Image is already in storage, exiting."
run-external 'machinectl' 'show-image' $output_image
machinectl show-image $output_image
return
}

logger info 'Deleting existing image'
run-external 'machinectl' 'remove' $output_image
try { machinectl stop $output_image }
try { machinectl remove $output_image }
}

if $config != null {
logger info "Applying configuration to machine."
let nspawn_config = $"($config_root)/($output_image).nspawn"
try {
if not ($output_image | path exists) or $override_config {
cp $config $nspawn_config
} else {
open $config | save $nspawn_config --append
}
} catch {
logger error "Failure when modifiying machine configuration, please run as root"
return
}
main config apply -y $config $output_image
}


try {
if $from_url != null {
logger info $"Pulling image from URL ($from_url)"
run-external 'machinectl' $"pull-($type)" $"--verify=($verify)" $"($from_url)" $"($output_image)"
} else {
logger info $"Pulling image ($image) tag ($tag)"
run-external 'machinectl' $"pull-($type)" $"--verify=($verify)" $"($full_image_name)" $"($output_image)"
}
machinectl $"pull-($type)" $"--verify=($verify)" $"(if $from_url != null { $from_url } else { $nspawnhub_image_url })" $"($output_image)"
} catch {
logger error "Failure when fetching image"
return
}

logger info "Removing read-only attribute from image"
try {
run-external 'machinectl' 'read-only' $"($output_image)" 'false'
machinectl read-only $"($output_image)" "false"
} catch {
logger error "Failure setting image as writable"
}

logger success "All done! This is your new machine:"
run-external 'machinectl' 'show-image' $"($output_image)" | lines | str trim
if $pull_only != null {
logger success "All done! This is your new machine:"
machinectl show-image $output_image | lines | str trim
}

logger info "Setting up passwordless root for container"
(run_container
--nspawn=$nspawn
$output_image
"mkdir -p /etc/nuspawn"
"echo "1" >> /etc/nuspawn/container" # For fancy shells when you want to know if you are in a container for your PS1 or virtual environment
"sed -i "s/^root.*/root::1::::::/g" /etc/shadow"
$"echo 'root::1::::::' >> /etc/shadow" # Will add a duplicate if the image already has a definition, but doesnt matter in the end
)
}
71 changes: 71 additions & 0 deletions src/lib/machine_manager.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export extern machinectl [
--verify: string = "no"
...args: string
]

export extern systemd-nspawn [
--machine (-M): string
...args: string
]

export extern systemd-run [
--uid: number
--gid: number
--pty (-t)
--quiet (-q)
...args: string
]

export extern "machinectl pull-tar" [url: string, name?: string]
export extern "machinectl pull-raw" [url: string, name?: string]
export extern "machinectl remove" [machine: string]
export extern "machinectl shell" [user_connection: string, ...args: string]
export extern "machinectl read-only" [machine: string, enabled: string]
export extern "machinectl show-image" [machine: string]
export extern "machinectl stop" [machine: string]

export const CONFIG_EXTENSION = "nspawn"

# Meant to be used as a way to run a single command at a time in a container using machinectl.
export def run_container [
--user: string = "root",
--nspawn (-n),
--environment (-e): string = "PATH=/usr/bin:/usr/local/bin:/bin" # Spaced environment variables for /usr/bin/env
--env-binary: path = /usr/bin/env
--shell-binary: path = /bin/sh
machine: string,
...args: string
] {
if not $nspawn {
machinectl start $machine
sleep 1sec
machinectl shell $"($user)@($machine)" $env_binary $environment $shell_binary '-c' ($args | str join " ; ")
} else {
(systemd-run
--uid=0
--gid=0
-t
-q
"--"
"systemd-nspawn"
"-M" $machine
$env_binary
$environment
$shell_binary
'-c' ($args | str join " ; ")
)
}
try { machinectl stop $machine | ignore }
}

# Meant to be used as a way to run a single command at a time in a container using nspawn.
export def nspawn_run_container [
--user: string = "root",
--environment (-e): string = "PATH=/usr/bin:/usr/local/bin:/bin" # Spaced environment variables for /usr/bin/env
--env-binary: path = /usr/bin/env
--shell-binary: path = /bin/sh
machine: string,
...args: string
] {

}
3 changes: 2 additions & 1 deletion src/lib/meta.nu
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const NAME = "nuspawn"
export const VERSION = "%VERSION%"
export const GIT_COMMIT = "%GIT_COMMIT%"
export const NSPAWNHUB_KEY_LOCATION = "https://hub.nspawn.org/storage/masterkey.pgp"
export const NSPAWNHUB_STORAGE_ROOT = "https://hub.nspawn.org/storage"
export const NSPAWNHUB_KEY_LOCATION = "https://hub.nspawn.org/storage/masterkey.pgp"
export const MACHINE_STORAGE_PATH = "/var/lib/machines"
export const MACHINE_CONFIG_PATH = "/etc/systemd/nspawn"
export const NUSPAWN_PROFILES_PATH = "/etc/nuspawn/profiles"
5 changes: 4 additions & 1 deletion src/lib/mod.nu
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export use prune.nu *
export use config.nu *
export use compose.nu *
export use remove.nu *
export use run.nu *
export use enter.nu *
export use machine_manager.nu *
export use verify.nu *
export use version.nu *
Loading

0 comments on commit 2f91959

Please sign in to comment.