Improve immutability and snapshots, blend-settings; Revamp blend

This commit is contained in:
Rudra Saraswat 2023-04-17 12:56:58 +05:30
parent 9f7dee08a8
commit de7e60e65e
13 changed files with 288 additions and 247 deletions

View file

@ -7,7 +7,9 @@ This repository also contains **blend-settings**, a tool for configuring blend a
## Credits ## Credits
The `init-blend` file in this repository uses a few lines (two sections) uses from distrobox's init script. These lines have been marked and attributed appropriately, and are licensed under [the GPL-3.0 license](https://github.com/89luca89/distrobox/blob/main/COPYING.md). The `init-blend` file in this repository uses a few lines (the sections have been clearly) uses from distrobox's init script. These lines have been marked and attributed appropriately, and are licensed under [the GPL-3.0 license](https://github.com/89luca89/distrobox/blob/main/COPYING.md).
I would also like to thank Luca Di Maio from Distrobox for NVIDIA driver support in containers.
Aside from these lines, all the other code in this repository has been written by me (rs2009). `blend-settings` is based on [Modren](https://github.com/RudraSwat/modren), a software store I (rs2009) had written long ago, and is licensed under the same license as the rest of the code in this repository, [the GPL-3.0 license](https://github.com/blend-os/blend/blob/main/LICENSE). Aside from these lines, all the other code in this repository has been written by me (rs2009). `blend-settings` is based on [Modren](https://github.com/RudraSwat/modren), a software store I (rs2009) had written long ago, and is licensed under the same license as the rest of the code in this repository, [the GPL-3.0 license](https://github.com/blend-os/blend/blob/main/LICENSE).

67
blend
View file

@ -111,7 +111,7 @@ def check_container(name):
return True return True
return False return False
def check_container_status(name): def check_container_status(name):
return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"") return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"")
def core_start_container(name): def core_start_container(name):
@ -128,7 +128,7 @@ def core_start_container(name):
logproc = pexpect.spawn('podman', args=['logs', '-f', '--since', str(start_time), name], timeout=300) logproc = pexpect.spawn('podman', args=['logs', '-f', '--since', str(start_time), name], timeout=300)
logproc.logfile_read = sys.stdout.buffer logproc.logfile_read = sys.stdout.buffer
logproc.expect('Completed container setup') logproc.expect('Completed container setup.')
logproc.terminate() logproc.terminate()
def core_create_container(): def core_create_container():
@ -189,11 +189,6 @@ def core_create_container():
core_start_container(name) core_start_container(name)
if distro == 'arch':
core_run_container('sudo pacman -Sy')
core_run_container('sudo pacman --noconfirm -Syu --needed git base-devel')
core_run_container('TEMP_DIR="$(mktemp -d)"; cd "${TEMP_DIR}"; git clone https://aur.archlinux.org/yay.git; cd yay; makepkg --noconfirm -si; rm -rf "${TEMP_DIR}"')
core_get_output = lambda cmd: subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip() core_get_output = lambda cmd: subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
host_get_output = lambda cmd: subprocess.run(['bash', '-c', cmd], host_get_output = lambda cmd: subprocess.run(['bash', '-c', cmd],
@ -399,57 +394,11 @@ if os.geteuid() == 0 and os.environ['BLEND_ALLOW_ROOT'] == None:
exit(1) exit(1)
description = f''' description = f'''
{colors.bold}{colors.fg.purple}Usage:{colors.reset}
blend [command] [options] [arguments]
{colors.bold}{colors.fg.purple}Version:{colors.reset} {__version}{colors.bold} {colors.bold}{colors.fg.purple}Version:{colors.reset} {__version}{colors.bold}
{colors.bold}{colors.bg.purple}blend{colors.reset}{colors.bold} is a package manager for {colors.bg.purple}blendOS{colors.reset}{colors.bold}, which includes support for Arch, Ubuntu and Fedora packages.{colors.reset} Use the 'blendOS Settings' app to create and manage Linux containers, Android apps and immutability configuration.
{colors.bold}{colors.fg.purple}default distro{colors.reset}: {colors.bold}{colors.fg.lightblue}arch{colors.reset} (default container's name is the same as that of the default distro) You can install and submit web apps from the Web Store.
Here's a list of the supported distros:
{colors.bold}1.{colors.reset} arch
{colors.bold}2.{colors.reset} fedora-rawhide
{colors.bold}3.{colors.reset} ubuntu-22.04
{colors.bold}4.{colors.reset} ubuntu-22.10
(debian support is coming soon)
You can use any of these distros by passing the option {colors.bold}--distro=[NAME OF THE DISTRO]{colors.reset}.
You can even install a supported desktop environment in a blend container (run `blend install-de [DESKTOP ENVIRONMENT NAME]` to install your favorite desktop environment).
However, this feature is still somewhat experimental, and some apps might be buggy.
Here's a list of the supported desktop environments:
{colors.bold}1.{colors.reset} gnome
{colors.bold}2.{colors.reset} mate
(support for many more DEs is coming soon)
{colors.bold}{colors.fg.lightblue}arch{colors.reset} also supports AUR packages, for an extremely large app catalog.
{colors.bold}{colors.fg.purple}available commands{colors.reset}:
{colors.bold}help{colors.reset} Show this help message and exit.
{colors.bold}version{colors.reset} Show version information and exit.
{colors.bold}enter{colors.reset} Enter the container shell.
{colors.bold}install{colors.reset} Install packages inside a container.
{colors.bold}remove{colors.reset} Remove packages inside a managed container.
{colors.bold}create-container{colors.reset} Create a container managed by blend.
{colors.bold}remove-container{colors.reset} Remove a container managed by blend.
{colors.bold}list-containers{colors.reset} List all the containers managed by blend.
{colors.bold}start-containers{colors.reset} Start all the container managed by blend.
{colors.bold}sync{colors.reset} Sync list of available packages from repository.
{colors.bold}search{colors.reset} Search for packages in a managed container.
{colors.bold}show{colors.reset} Show details about a package.
{colors.bold}update{colors.reset} Update all the packages in a managed container.
{colors.bold}{colors.fg.purple}options for commands{colors.reset}:
{colors.bold}-cn CONTAINER NAME, --container-name CONTAINER NAME{colors.reset}
set the container name (the default is the name of the distro)
{colors.bold}-d DISTRO, --distro DISTRO{colors.reset}
set the distro name (supported: arch fedora-rawhide ubuntu-22.04 ubuntu-22.10; default is arch)
{colors.bold}-y, --noconfirm{colors.reset} assume yes for all questions
{colors.bold}-v, --version{colors.reset} show version information and exit
''' '''
epilog = f''' epilog = f'''
@ -458,17 +407,13 @@ epilog = f'''
parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRESS, parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRESS,
epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
command_map = { 'install': install_blend, command_map = { 'enter': enter_container,
'remove': remove_blend, 'exec': enter_container,
'enter': enter_container,
'create-container': core_create_container, 'create-container': core_create_container,
'remove-container': remove_container, 'remove-container': remove_container,
'list-containers': list_containers, 'list-containers': list_containers,
'start-containers': start_containers, 'start-containers': start_containers,
'sync': sync_blends, 'sync': sync_blends,
'update': update_blends,
'search': search_blend,
'show': show_blend,
'help': 'help', 'help': 'help',
'version': 'version' } 'version': 'version' }
parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS) parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS)

View file

@ -1,9 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os, sys, yaml, time, getpass, shutil, fileinput, subprocess import os
import sys
import yaml
import time
import getpass
import shutil
import fileinput
import subprocess
def get_containers(): def get_containers():
container_list = subprocess.run(['sudo', '-u', user, 'podman', 'ps', '-a', '--no-trunc', '--size', '--sort=created', '--format', container_list = subprocess.run(['sudo', '-u', user, 'podman', 'ps', '-a', '--no-trunc', '--sort=created', '--format',
'{{.Names}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n') '{{.Names}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n')
try: try:
@ -22,6 +30,7 @@ def get_containers():
except: except:
return container_list return container_list
def list_use_container_bin(): def list_use_container_bin():
try: try:
with open(os.path.expanduser('~/.config/blend/config.yaml')) as config_file: with open(os.path.expanduser('~/.config/blend/config.yaml')) as config_file:
@ -30,23 +39,25 @@ def list_use_container_bin():
except: except:
return [] return []
def check_if_present(attr, desktop_str): def check_if_present(attr, desktop_str):
for l in desktop_str: for l in desktop_str:
if l.startswith(attr + '='): if l.startswith(attr + '='):
return True return True
return False return False
def which(bin): def which(bin):
results = [] results = []
for dir in os.environ.get('PATH').split(':'): for dir in os.environ.get('PATH').split(':'):
if os.path.isdir(dir): if os.path.isdir(dir):
for i in os.listdir(dir): if os.path.basename(bin) in os.listdir(dir):
if os.path.basename(bin) == i: results.append(os.path.join(dir, os.path.basename(bin)))
results.append(os.path.join(dir, i))
if results == []: if results == []:
return None return None
return results return results
def create_container_binaries(): def create_container_binaries():
_binaries = [] _binaries = []
remove_binaries = [] remove_binaries = []
@ -54,17 +65,18 @@ def create_container_binaries():
for c in _list: for c in _list:
c = c.strip() c = c.strip()
for i in con_get_output(c, '''find /usr/bin -type f -printf "%P\n" 2>/dev/null; for i in con_get_output(c, '''find /usr/bin -type f -printf "%P\n" 2>/dev/null;
find /usr/local/bin -type f -printf "%P\n" 2>/dev/null; find /usr/local/bin -type f -printf "%P\n" 2>/dev/null;''').split('\n'):
find /usr/sbin -type f -printf "%P\n" 2>/dev/null;''').split('\n'):
i = i.strip() i = i.strip()
os.makedirs(os.path.expanduser(f'~/.local/bin/blend_{c}'), exist_ok=True) os.makedirs(os.path.expanduser(
f'~/.local/bin/blend_{c}'), exist_ok=True)
i_present = False i_present = False
orig_which_out = which(os.path.basename(i)) orig_which_out = which(os.path.basename(i))
which_out = None which_out = None
if orig_which_out != None: if orig_which_out != None:
which_out = orig_which_out.copy() which_out = orig_which_out.copy()
try: try:
which_out.remove(os.path.expanduser(f'~/.local/bin/blend_bin/{os.path.basename(i)}')) which_out.remove(os.path.expanduser(
f'~/.local/bin/blend_bin/{os.path.basename(i)}'))
except ValueError: except ValueError:
pass pass
if which_out == []: if which_out == []:
@ -74,13 +86,17 @@ def create_container_binaries():
if os.path.basename(i) != 'host-spawn' and i != '' and not i_present: if os.path.basename(i) != 'host-spawn' and i != '' and not i_present:
with open(os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 'w') as f: with open(os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 'w') as f:
f.write('#!/bin/sh\n') f.write('#!/bin/bash\n')
f.write(f'# blend container: {i}\n') f.write(f'# blend container: {c};{i}\n')
if os.path.basename(i) in _exceptions: if os.path.basename(i) in _exceptions:
f.write(f'# EXCEPTION\n') f.write(f'# EXCEPTION\n')
f.write(f'BLEND_ALLOW_ROOT= BLEND_NO_CHECK= blend enter -cn {c} -- {i} "$@"\n') f.write('[ -f /run/.containerenv ] && { if [[ -e "/usr/bin/' + os.path.basename(i) + '" ]] || [[ -e "/usr/local/bin/' + os.path.basename(i) + '" ]]; then if [[ -e "/usr/bin/' + os.path.basename(i) + '" ]]; then /usr/bin/' + os.path.basename(
i) + ' "$@"; elif [[ -e "/usr/local/bin/' + os.path.basename(i) + '" ]]; then /usr/local/bin/' + os.path.basename(i) + ' "$@"; fi; exit $?; else echo "This command can be accessed from the host, or from the container \'' + c + '\'."; exit 127; fi } || :\n')
f.write(
f'BLEND_ALLOW_ROOT= BLEND_NO_CHECK= blend enter -cn {c} -- {os.path.basename(i)} "$@"\n')
# XXX: make this bit fully atomic # XXX: make this bit fully atomic
os.chmod(os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 0o775) os.chmod(os.path.expanduser(
f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), 0o775)
subprocess.call(['mv', os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'), subprocess.call(['mv', os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}.tmp'),
os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}')]) os.path.expanduser(f'~/.local/bin/blend_{c}/{os.path.basename(i)}')])
_binaries.append((c, os.path.basename(os.path.basename(i)))) _binaries.append((c, os.path.basename(os.path.basename(i))))
@ -89,11 +105,15 @@ def create_container_binaries():
for c, i in _binaries: for c, i in _binaries:
try: try:
os.symlink(os.path.expanduser(f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) if i == 'apt':
print(c, i)
os.symlink(os.path.expanduser(
f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}'))
except FileExistsError: except FileExistsError:
if not subprocess.call(['grep', '-q', f'^# container: {c}$', os.path.expanduser(f'~/.local/bin/blend_bin/{i}')]): if subprocess.call(['grep', '-q', f'^# container: {c};{i}$', os.path.expanduser(f'~/.local/bin/blend_bin/{i}')], shell=False):
os.remove(os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) os.remove(os.path.expanduser(f'~/.local/bin/blend_bin/{i}'))
os.symlink(os.path.expanduser(f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}')) os.symlink(os.path.expanduser(
f'~/.local/bin/blend_{c}/{i}'), os.path.expanduser(f'~/.local/bin/blend_bin/{i}'))
for i in remove_binaries: for i in remove_binaries:
try: try:
@ -103,12 +123,15 @@ def create_container_binaries():
for b in os.listdir(os.path.expanduser(f'~/.local/bin/blend_bin')): for b in os.listdir(os.path.expanduser(f'~/.local/bin/blend_bin')):
if [_b for _b in _binaries if _b[1] == b] == []: if [_b for _b in _binaries if _b[1] == b] == []:
os.remove(os.path.join(os.path.expanduser(f'~/.local/bin/blend_bin'), b)) os.remove(os.path.join(os.path.expanduser(
f'~/.local/bin/blend_bin'), b))
def create_container_applications(): def create_container_applications():
_apps = [] _apps = []
os.makedirs(os.path.expanduser(f'~/.local/share/applications'), exist_ok=True) os.makedirs(os.path.expanduser(
f'~/.local/share/applications'), exist_ok=True)
for c in _list: for c in _list:
c = c.strip() c = c.strip()
@ -116,11 +139,13 @@ def create_container_applications():
orig_path = i.strip() orig_path = i.strip()
i = os.path.basename(orig_path) i = os.path.basename(orig_path)
i_present = (os.path.isfile(f'/usr/share/applications/{i}') or os.path.isfile(f'/usr/local/share/applications/{i}') i_present = (os.path.isfile(f'/usr/share/applications/{i}') or os.path.isfile(f'/usr/local/share/applications/{i}')
or os.path.isfile(os.path.expanduser(f'~/.local/share/applications/{i}'))) or os.path.isfile(os.path.expanduser(f'~/.local/share/applications/{i}')))
if i != '' and not i_present: if i != '' and not i_present:
with open(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), 'w') as f: with open(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), 'w') as f:
_ = con_get_output(c, f"sudo sed -i '/^DBusActivatable=/d' {orig_path}") _ = con_get_output(
_ = con_get_output(c, f"sudo sed -i '/^TryExec=/d' {orig_path}") c, f"sudo sed -i '/^DBusActivatable=/d' {orig_path}")
_ = con_get_output(
c, f"sudo sed -i '/^TryExec=/d' {orig_path}")
contents = con_get_output(c, f'cat {orig_path}') contents = con_get_output(c, f'cat {orig_path}')
f.write(contents) f.write(contents)
for line in fileinput.input(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), inplace=True): for line in fileinput.input(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), inplace=True):
@ -129,16 +154,19 @@ def create_container_applications():
elif line.strip().startswith('Icon='): elif line.strip().startswith('Icon='):
if '/' in line: if '/' in line:
line = line.strip() line = line.strip()
_ = con_get_output(c, f"mkdir -p ~/.local/share/blend/icons/file/\"{c}_{i}\"; cp {line[5:]} ~/.local/share/blend/icons/file/\"{c}_{i}\"") _ = con_get_output(
c, f"mkdir -p ~/.local/share/blend/icons/file/\"{c}_{i}\"; cp {line[5:]} ~/.local/share/blend/icons/file/\"{c}_{i}\"")
line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/file/" + c + "_" + i + "/" + os.path.basename(line[5:]))}\n' line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/file/" + c + "_" + i + "/" + os.path.basename(line[5:]))}\n'
else: else:
line = line.strip() line = line.strip()
icons = con_get_output(c, f'''find /usr/share/icons /usr/share/pixmaps /var/lib/flatpak/exports/share/icons \\ icons = con_get_output(c, f'''find /usr/share/icons /usr/share/pixmaps /var/lib/flatpak/exports/share/icons \\
-type f -iname "*{line[5:]}*" 2> /dev/null | sort''').split('\r\n') -type f -iname "*{line[5:]}*" 2> /dev/null | sort''').split('\r\n')
_ = con_get_output(c, f"mkdir -p ~/.local/share/blend/icons/\"{c}_{i}\"; cp {icons[0]} ~/.local/share/blend/icons/\"{c}_{i}\"") _ = con_get_output(
c, f"mkdir -p ~/.local/share/blend/icons/\"{c}_{i}\"; cp {icons[0]} ~/.local/share/blend/icons/\"{c}_{i}\"")
line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/" + c + "_" + i + "/" + os.path.basename(icons[0]))}\n' line = f'Icon={os.path.expanduser("~/.local/share/blend/icons/" + c + "_" + i + "/" + os.path.basename(icons[0]))}\n'
sys.stdout.write(line) sys.stdout.write(line)
os.chmod(os.path.expanduser(f'~/.local/share/applications/blend;{i}'), 0o775) os.chmod(os.path.expanduser(
f'~/.local/share/applications/blend;{i}'), 0o775)
_apps.append((c, i)) _apps.append((c, i))
del _ del _
@ -146,7 +174,9 @@ def create_container_applications():
if a.startswith('blend;'): if a.startswith('blend;'):
a = a.removeprefix('blend;') a = a.removeprefix('blend;')
if [_a for _a in _apps if _a[1] == a] == []: if [_a for _a in _apps if _a[1] == a] == []:
os.remove(os.path.expanduser(f'~/.local/share/applications/blend;{a}')) os.remove(os.path.expanduser(
f'~/.local/share/applications/blend;{a}'))
def create_container_sessions(type='xsessions'): def create_container_sessions(type='xsessions'):
session_dir = f'/usr/share/{type}' session_dir = f'/usr/share/{type}'
@ -176,10 +206,13 @@ def create_container_sessions(type='xsessions'):
continue continue
sys.stdout.write(line) sys.stdout.write(line)
os.chmod(os.path.expanduser(f'{session_dir}/blend-{c};{i}'), 0o775) os.chmod(os.path.expanduser(
f'{session_dir}/blend-{c};{i}'), 0o775)
def con_get_output(name, cmd): return subprocess.run(['sudo', '-u', user, 'podman', 'exec', '--user', getpass.getuser(), '-it', name, 'bash', '-c', cmd],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
con_get_output = lambda name, cmd: subprocess.run(['sudo', '-u', user, 'podman', 'exec', '--user', getpass.getuser(), '-it', name, 'bash', '-c', cmd],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
user = getpass.getuser() user = getpass.getuser()
@ -188,15 +221,6 @@ try:
except: except:
pass pass
try:
if sys.argv[1] == 'sessions':
_list = get_containers()
create_container_sessions(type='xsessions')
create_container_sessions(type='wayland-sessions')
exit(0)
except IndexError:
pass
for c in get_containers(): for c in get_containers():
c = c.strip() c = c.strip()
subprocess.call(['podman', 'start', c]) subprocess.call(['podman', 'start', c])
@ -207,4 +231,4 @@ while True:
create_container_binaries() create_container_binaries()
create_container_applications() create_container_applications()
time.sleep(6) time.sleep(15)

View file

@ -13,10 +13,10 @@
<body style="height: 100%;"> <body style="height: 100%;">
<br> <br>
<div class="topnav"> <div class="topnav">
<div class="btn-group" role="group" aria-label="Stores"> <div class="btn-group" id="settings-tabs" role="group" aria-label="Stores">
<button class="btn btn-outline-light active shadow-none" id="containers-button" onclick="page('containers')">Linux <button class="btn btn-outline-light active shadow-none" id="containers-button" onclick="page('containers')">Linux
Containers</button> Containers</button>
<button class="btn btn-outline-light shadow-none" id="android-button" onclick="page('android')">Android <button class="btn btn-outline-light shadow-none d-none" id="android-button" onclick="page('android')">Android
Apps</button> Apps</button>
<button class="btn btn-outline-light shadow-none" id="system-button" onclick="page('system')">System</button> <button class="btn btn-outline-light shadow-none" id="system-button" onclick="page('system')">System</button>
</div> </div>
@ -41,6 +41,10 @@
const $ = require("jquery") const $ = require("jquery")
if (fs.existsSync('/usr/bin/waydroid')) {
document.getElementById('android-button').classList.remove('d-none')
}
function page(page) { function page(page) {
switch (page) { switch (page) {
case 'containers': case 'containers':

View file

@ -1,135 +1,136 @@
function rollback() {
let rollback_worker = new Worker(
`data:text/javascript,
let s = require('child_process').spawnSync('pkexec', ['blend-system', 'rollback']).status
if (s === 0) {
postMessage('success')
} else {
postMessage('failure')
}
`
)
rollback_worker.onmessage = e => {
if (e.data == 'success') {
document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" onclick="undo_rollback()" id="rollback-btn">Cancel rollback</button>'
} else {
document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" id="rollback-btn" disabled>Failed</button>'
setTimeout(() => document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" onclick="rollback()" id="rollback-btn">Rollback</button>', 2000)
}
}
}
function undo_rollback() {
let undo_rollback_worker = new Worker(
`data:text/javascript,
let s = require('child_process').spawnSync('pkexec', ['rm', '-f', '/blend/states/.load_prev_state']).status
if (s === 0) {
postMessage('success')
} else {
postMessage('failure')
}
`
)
undo_rollback_worker.onmessage = e => {
if (e.data == 'success') {
document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" onclick="rollback()" id="rollback-btn">Rollback</button>'
} else {
document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" id="rollback-btn" disabled>Failed</button>'
setTimeout(() => document.getElementById('rollback-btn').outerHTML =
'<button type="button" class="btn btn-danger" onclick="undo_rollback()" id="rollback-btn">Cancel rollback</button>', 2000)
}
}
}
function init_waydroid() { function init_waydroid() {
document.getElementById('initialize-btn').outerHTML = document.getElementById('initialize-btn').outerHTML =
'<button type="button" id="initialize-btn" onclick="init_waydroid()" class="btn btn-primary" disabled>Initializing...</button>' '<button type="button" id="initialize-btn" onclick="init_waydroid()" class="btn btn-primary" disabled>Initializing...</button>'
let init_worker = new Worker( let init_worker = new Worker(
`data:text/javascript, `data:text/javascript,
require('child_process').spawnSync('pkexec', ['waydroid', 'init']) require('child_process').spawnSync('pkexec', ['waydroid', 'init'])
require('child_process').spawnSync('pkexec', ['systemctl', 'enable', '--now', 'waydroid-container'])
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown']) require('child_process').spawn('sh', ['-c', 'waydroid session start & disown'])
setTimeout(() => { setTimeout(() => {
require('child_process').spawnSync('pkexec', ['waydroid', 'shell', 'pm', 'disable', 'com.android.inputmethod.latin']) require('child_process').spawnSync('pkexec', ['waydroid', 'shell', 'pm', 'disable', 'com.android.inputmethod.latin'])
require('child_process').spawnSync('waydroid', ['prop', 'set', 'persist.waydroid.multi_windows', 'true']) require('child_process').spawnSync('waydroid', ['prop', 'set', 'persist.waydroid.multi_windows', 'true'])
postMessage('success') if (require('child_process').spawnSync('sh', ['-c', 'LC_ALL=C glxinfo | grep "^OpenGL renderer string: "']).stdout.includes('NVIDIA')) {
require('child_process').spawnSync('sh', ['-c', 'echo "ro.hardware.gralloc=default" | pkexec tee -a /var/lib/waydroid/waydroid.cfg'])
require('child_process').spawnSync('sh', ['-c', 'echo "ro.hardware.egl=swiftshader" | pkexec tee -a /var/lib/waydroid/waydroid.cfg'])
}
require('child_process').spawn('sh', ['-c', 'pkexec waydroid upgrade -o; waydroid session stop; waydroid session start'])
setTimeout(() => { postMessage('success') }, 1000)
}, 2000) }, 2000)
` `
) )
init_worker.onmessage = e => { init_worker.onmessage = e => {
if (e.data == 'success') { if (e.data == 'success') {
document.getElementById('init-waydroid').classList.add('d-none') document.getElementById('waydroid-initialize-settings').classList.add('d-none')
document.getElementById('waydroid-initialized-settings').classList.remove('d-none') document.getElementById('waydroid-initialized-settings').classList.remove('d-none')
} }
} }
} }
function enable_multi_window() { function install_aurora_store() {
document.getElementById('multiwindow-btn').outerHTML = document.getElementById('aurora-store-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary" disabled>Enabling...</button>' `<button type="button" id="aurora-store-btn" onclick="install_aurora_store()"
let multi_window_worker = new Worker( class="btn btn-success" disabled>Installing...</button>`
let aurora_store_worker = new Worker(
`data:text/javascript, `data:text/javascript,
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown']) require('child_process').spawnSync('sh', ['-c', 'mkdir -p ~/.cache/blend-settings; rm -f ~/.cache/blend-settings/aurora.apk'])
setTimeout(() => { require('child_process').spawnSync('waydroid', ['prop', 'set', 'persist.waydroid.multi_windows', 'true']); require('child_process').spawn('sh', ['-c', 'waydroid session stop']); postMessage('success') }, 500) let s1 = require('child_process').spawnSync('sh', ['-c', 'wget -O ~/.cache/blend-settings/aurora.apk https://gitlab.com/AuroraOSS/AuroraStore/uploads/bbc1bd5a77ab2b40bbf288ccbef8d1f0/AuroraStore_4.1.1.apk']).status
if (s1 != 0) {
postMessage('failed')
} else {
require('child_process').spawn('waydroid', ['session', 'start'])
setTimeout(() => {
require('child_process').spawnSync('sh', ['-c', 'waydroid app install ~/.cache/blend-settings/aurora.apk'])
setTimeout(() => postMessage('success'), 200)
}, 2000)
}
` `
) )
multi_window_worker.onmessage = e => { aurora_store_worker.onmessage = e => {
if (e.data == 'success') { if (e.data == 'success') {
document.getElementById('multiwindow-btn').outerHTML = document.getElementById('aurora-store-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="disable_multi_window()" class="btn btn-primary">Disable</button>' `<button type="button btn-success" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success" disabled>Installed</button>`
} else if (e.data == 'failed') {
document.getElementById('aurora-store-btn').outerHTML =
`<button type="button btn-success" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success" disabled>Failed</button>`
setTimeout(() => {
document.getElementById('aurora-store-btn').outerHTML =
`<button type="button btn-success" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success">Install</button>`
}, 2000)
} }
} }
} }
function disable_multi_window() { function install_f_droid() {
document.getElementById('multiwindow-btn').outerHTML = document.getElementById('f-droid-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary" disabled>Disabling...</button>' `<button type="button" id="f-droid-btn" onclick="install_f_droid()"
let multi_window_worker = new Worker( class="btn btn-primary" disabled>Installing...</button>`
let f_droid_worker = new Worker(
`data:text/javascript, `data:text/javascript,
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown']) require('child_process').spawnSync('sh', ['-c', 'mkdir -p ~/.cache/blend-settings; rm -f ~/.cache/blend-settings/f-droid.apk'])
setTimeout(() => { require('child_process').spawnSync('waydroid', ['prop', 'set', 'persist.waydroid.multi_windows', 'false']); require('child_process').spawn('sh', ['-c', 'waydroid session stop']); postMessage('success') }, 500) let s1 = require('child_process').spawnSync('sh', ['-c', 'wget -O ~/.cache/blend-settings/f-droid.apk https://f-droid.org/F-Droid.apk']).status
if (s1 != 0) {
postMessage('failed')
} else {
require('child_process').spawn('waydroid', ['session', 'start'])
setTimeout(() => {
require('child_process').spawnSync('sh', ['-c', 'waydroid app install ~/.cache/blend-settings/f-droid.apk'])
setTimeout(() => postMessage('success'), 200)
}, 2000)
}
` `
) )
multi_window_worker.onmessage = e => { f_droid_worker.onmessage = e => {
if (e.data == 'success') { if (e.data == 'success') {
document.getElementById('multiwindow-btn').outerHTML = document.getElementById('f-droid-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary">Enable</button>' `<button type="button btn-success" id="f-droid-btn" onclick="install_f_droid()"
class="btn btn-primary" disabled>Installed</button>`
} else if (e.data == 'failed') {
document.getElementById('f-droid-btn').outerHTML =
`<button type="button btn-success" id="f-droid-btn" onclick="install_f_droid()"
class="btn btn-primary" disabled>Failed</button>`
setTimeout(() => {
document.getElementById('f-droid-btn').outerHTML =
`<button type="button btn-success" id="f-droid-btn" onclick="install_f_droid()"
class="btn btn-primary">Install</button>`
}, 2000)
} }
} }
} }
function check_multi_window_enabled() { function waydroid_open_settings() {
let check_worker = new Worker( require('child_process').spawn('waydroid', ['app', 'launch', 'com.android.settings'])
`data:text/javascript,
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown'])
setTimeout(() => { let val = require('child_process').spawnSync('waydroid', ['prop', 'get', 'persist.waydroid.multi_windows']).stdout; postMessage(val) }, 500)
`
)
check_worker.onmessage = e => {
if (new TextDecoder("utf-8").decode(e.data).trim() == 'true') {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="disable_multi_window()" class="btn btn-primary">Disable</button>'
} else {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary">Enable</button>'
}
}
} }
require('fs').stat('/var/lib/waydroid', (err, stat) => { require('fs').stat('/var/lib/waydroid', (err, stat) => {
if (err == null) { if (err == null) {
document.getElementById('waydroid-initialize-settings').classList.add('d-none') document.getElementById('waydroid-initialize-settings').classList.add('d-none')
document.getElementById('waydroid-initialized-settings').classList.remove('d-none') document.getElementById('waydroid-initialized-settings').classList.remove('d-none')
if (require('child_process').spawnSync('sh', ['-c', 'LC_ALL=C glxinfo | grep "^OpenGL renderer string: "']).stdout.includes('NVIDIA')) {
document.getElementById('nvidia-warning-installed').classList.remove('d-none')
}
require('child_process').spawn('waydroid', ['session', 'start'])
setTimeout(() => {
if (require('child_process').spawnSync('waydroid', ['app', 'list']).stdout.includes('com.aurora.store')) {
document.getElementById('aurora-store-btn').outerHTML =
`<button type="button btn-success" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success" disabled>Installed</button>`
}
if (require('child_process').spawnSync('waydroid', ['app', 'list']).stdout.includes('org.fdroid.fdroid')) {
document.getElementById('f-droid-btn').outerHTML =
`<button type="button btn-success" id="fdroid-btn" onclick="install_f_droid()"
class="btn btn-primary" disabled>Installed</button>`
}
}, 1000)
} else {
if (require('child_process').spawnSync('sh', ['-c', 'LC_ALL=C glxinfo | grep "^OpenGL renderer string: "']).stdout.includes('NVIDIA')) {
document.getElementById('nvidia-warning').classList.remove('d-none')
}
} }
}) })
check_state_creation()
check_rollback()
$('#automatic-state-toggle').on('change', () => { $('#automatic-state-toggle').on('change', () => {
if (!document.getElementById('automatic-state-toggle').checked) { if (!document.getElementById('automatic-state-toggle').checked) {
let enable_autostate_worker = new Worker( let enable_autostate_worker = new Worker(

View file

@ -47,6 +47,8 @@ function undo_rollback() {
} }
function save_state() { function save_state() {
$("#settings-tabs").find("*").prop('disabled', true)
let save_state_worker = new Worker( let save_state_worker = new Worker(
`data:text/javascript, `data:text/javascript,
let s = require('child_process').spawnSync('pkexec', ['blend-system', 'save-state']).status let s = require('child_process').spawnSync('pkexec', ['blend-system', 'save-state']).status
@ -61,11 +63,13 @@ function save_state() {
if (e.data == 'success') { if (e.data == 'success') {
document.getElementById('save-state-btn').outerHTML = document.getElementById('save-state-btn').outerHTML =
'<button type="button" class="btn btn-success" id="save-state-btn" disabled>Saved state</button>' '<button type="button" class="btn btn-success" id="save-state-btn" disabled>Saved state</button>'
$("#settings-tabs").find("*").prop('disabled', false)
setTimeout(() => document.getElementById('save-state-btn').outerHTML = setTimeout(() => document.getElementById('save-state-btn').outerHTML =
'<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button>', 2000) '<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button>', 2000)
} else { } else {
document.getElementById('save-state-btn').outerHTML = document.getElementById('save-state-btn').outerHTML =
'<button type="button" class="btn btn-success" id="save-state-btn" disabled>Failed</button>' '<button type="button" class="btn btn-success" id="save-state-btn" disabled>Failed</button>'
$("#settings-tabs").find("*").prop('disabled', false)
setTimeout(() => document.getElementById('save-state-btn').outerHTML = setTimeout(() => document.getElementById('save-state-btn').outerHTML =
'<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button>', 2000) '<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button>', 2000)
} }

View file

@ -1,12 +1,12 @@
<div class="container-fluid d-flex justify-content-center"> <div class="container-fluid d-flex justify-content-center">
<div class="col-12 col-lg-10 col-xl-8 mx-auto"> <div class="col-12 col-lg-10 col-xl-8 mx-auto">
<div id="waydroid-initialize-settings"> <div id="waydroid-initialize-settings">
<div class="list-group mt-3 mb-5 shadow" id="waydroid-initialize-settings"> <div class="list-group mt-3 mb-5" id="waydroid-initialize-settings">
<div class="list-group-item"> <div class="list-group-item shadow">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">
<strong class="mb-0">Initialize Android App Support</strong> <strong class="mb-0">Initialize Android app support</strong>
<p class="text-muted mb-0">Initialize WayDroid to be able to run Android apps.</p> <p class="text-muted mb-0">You may be asked to enter your password repeatedly to initialize WayDroid.</p>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="form-check form-switch align-middle"> <div class="form-check form-switch align-middle">
@ -16,57 +16,34 @@
</div> </div>
</div> </div>
</div> </div>
<br>
<p id="nvidia-warning" class="d-none">Since you're using an <b style="color: lightgreen;">NVIDIA</b> GPU,
Android apps will use software rendering.</p>
<p>After initializing WayDroid, you can install a store (or many) of your choice.</p>
</div> </div>
</div> </div>
<div class="d-none" id="waydroid-initialized-settings"> <div class="d-none" id="waydroid-initialized-settings">
<div id="main-list" class="mb-3"> <p>Android app support (WayDroid) has successfully been initialized.<span id="nvidia-warning-installed"
<div class="list-group-item" id=""> class="d-none"> Since you're using an <b style="color: lightgreen;">NVIDIA</b> GPU, apps will use software
<div class="row align-items-center"> rendering.</span></p>
<div class="col">
<strong class="mb-0">App Lounge (/e/)</strong>
<p class="text-muted mb-0">An installable catalogue of FOSS Android applications.</p>
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
<button type="button" id="e-applounge-inst-btn" onclick="install_e_applounge()" class="btn btn-primary"
disabled>Checking status...</button>
</div>
</div>
</div>
</div>
</div>
<strong>Install a store</strong> <strong>Install a store</strong>
<div class="list-group mt-3 mb-4 shadow"> <div class="list-group mt-3 mb-4 shadow">
<div> <div>
<div class="list-group-item" id="multi-window"> <div class="list-group-item" id="aurora-store-item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">
<strong class="mb-0">App Lounge (/e/)</strong> <strong class="mb-0">Aurora Store</strong>
<p class="text-muted mb-0">An installable catalogue of FOSS Android applications.</p>
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
<button type="button" id="e-applounge-inst-btn" onclick="install_e_applounge()"
class="btn btn-primary" disabled>Checking status...</button>
</div>
</div>
</div>
</div>
<div class="list-group-item" id="multi-window">
<div class="row align-items-center">
<div class="col">
<strong class="mb-0">Aurora Store (Nightly)</strong>
<p class="text-muted mb-0">An open-source Google Play Store client.</p> <p class="text-muted mb-0">An open-source Google Play Store client.</p>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="form-check form-switch align-middle"> <div class="form-check form-switch align-middle">
<button type="button" id="aurora-store-inst-btn" onclick="install_aurora_store()" <button type="button" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-primary" disabled>Checking status...</button> class="btn btn-success" disabled>Checking status...</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="list-group-item" id="multi-window"> <div class="list-group-item" id="f-droid-item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">
<strong class="mb-0">F-Droid</strong> <strong class="mb-0">F-Droid</strong>
@ -74,14 +51,19 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="form-check form-switch align-middle"> <div class="form-check form-switch align-middle">
<button type="button" id="f-droid-btn" onclick="install_f_droid()" class="btn btn-primary" <button type="button" id="f-droid-btn" onclick="install_f_droid()"
disabled>Checking status...</button> class="btn btn-primary" disabled>Checking status...</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button type="button" id="f-droid-btn" onclick="waydroid_open_settings()"
class="btn btn-primary">Open Settings</button>
<br><br>
<strong>Useful information</strong>
<p>In the event that an app you regularly use on your phone is broken on blendOS, you could install MicroG from F-Droid by following the instructions <a href="javascript:void()" onclick="require('electron').shell.openExternal('https://microg.org/download.html')">here</a>.</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,8 +1,8 @@
<div class="container-fluid d-flex justify-content-center"> <div class="container-fluid d-flex justify-content-center">
<div class="col-12 col-lg-10 col-xl-8 mx-auto"> <div class="col-12 col-lg-10 col-xl-8 mx-auto">
<strong class="mb-0">Containers</strong> <strong class="mb-0">Containers</strong>
<p>You can install any app from any of the supported distributions (Arch, Fedora, and Ubuntu). Any apps you install in them will appear as regular applications on your system, and so will any binaries. In case a binary is common between two containers, the binary from the most recently created container will be exported. You can override this by rearranging (dragging) the containers below to select the priority that should be assigned to each container.</p> <p>You can install any app from any of the supported distributions (<b>Arch</b>, <b>Fedora</b>, and <b>Ubuntu</b>). Apps you install will appear as regular applications on your system (as well as binaries and package managers). You can override the priority in which common binaries are made available on the system by rearranging (dragging) the containers below to select the priority that should be assigned to each container.</p>
<div class="list-group mb-5 shadow" id="container-list"> <div class="list-group mb-4 shadow" id="container-list">
<div class="list-group-item"> <div class="list-group-item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">
@ -18,6 +18,7 @@
<div class="container-fluid d-flex justify-content-center"> <div class="container-fluid d-flex justify-content-center">
<div class="col-12 col-lg-10 col-xl-8 mx-auto"> <div class="col-12 col-lg-10 col-xl-8 mx-auto">
<strong class="mb-0">Create new container</strong> <strong class="mb-0">Create new container</strong>
<p>Create a container for each distribution to be able to use their package managers and other binaries directly from a terminal.</p>
<form onsubmit="create_container(); return false"> <form onsubmit="create_container(); return false">
<div class="form-group row"> <div class="form-group row">
<label for="inputContainerName" class="col-sm-3 col-form-label">Container name</label> <label for="inputContainerName" class="col-sm-3 col-form-label">Container name</label>

View file

@ -7,13 +7,12 @@
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col"> <div class="col">
<strong class="mb-0">Disable automatic state creation</strong> <strong class="mb-0">Disable automatic state creation</strong>
<p class="text-muted mb-0">blendOS creates copies of apps and config every 6 hours (and keeps the <p class="text-muted mb-0">blendOS creates copies of apps and config every 12 hours (and keeps the
last two).</p> previous one).</p>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="form-check form-switch align-middle"> <div class="form-check form-switch align-middle">
<input class="form-check-input align-middle" type="checkbox" role="switch" <input class="form-check-input align-middle" type="checkbox" role="switch" id="automatic-state-toggle">
id="automatic-state-toggle">
</div> </div>
</div> </div>
</div> </div>
@ -26,7 +25,8 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="form-check form-switch align-middle"> <div class="form-check form-switch align-middle">
<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button> <button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save
state</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -80,6 +80,10 @@
fit.fit() fit.fit()
ipc.on("terminal.incomingData", (event, data) => { ipc.on("terminal.incomingData", (event, data) => {
fit.fit();
term.resize(term.cols, term.rows)
ipc.send("terminal.resize", [term.cols, term.rows])
term.write(data); term.write(data);
}); });

View file

@ -99,7 +99,7 @@ def autosave_state():
while True: while True:
if not os.path.isfile('/blend/states/.disable_states'): if not os.path.isfile('/blend/states/.disable_states'):
save_state() save_state()
time.sleep(6*60*60) # XXX: make this configurable time.sleep(12*60*60) # XXX: make this configurable
def toggle_states(): def toggle_states():
if os.path.isfile('/blend/states/.disable_states'): if os.path.isfile('/blend/states/.disable_states'):
@ -125,7 +125,6 @@ description = f'''
{colors.bold}{colors.fg.purple}available commands{colors.reset}: {colors.bold}{colors.fg.purple}available commands{colors.reset}:
{colors.bold}help{colors.reset} Show this help message and exit. {colors.bold}help{colors.reset} Show this help message and exit.
{colors.bold}version{colors.reset} Show version information and exit. {colors.bold}version{colors.reset} Show version information and exit.
{colors.bold}load-overlay{colors.reset} Load the current overlay.
{colors.bold}save-state{colors.reset} Save the current state (backup). {colors.bold}save-state{colors.reset} Save the current state (backup).
{colors.bold}toggle-states{colors.reset} Enable/disable automatic state creation (you can still manually save states). {colors.bold}toggle-states{colors.reset} Enable/disable automatic state creation (you can still manually save states).
{colors.bold}rollback{colors.reset} Rollback to previous state. {colors.bold}rollback{colors.reset} Rollback to previous state.
@ -142,7 +141,6 @@ parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRES
epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
command_map = { 'help': 'help', command_map = { 'help': 'help',
'version': 'version', 'version': 'version',
'load-overlay': load_overlay,
'save-state': save_state, 'save-state': save_state,
'toggle-states': toggle_states, 'toggle-states': toggle_states,
'autosave-state': autosave_state, 'autosave-state': autosave_state,

View file

@ -17,14 +17,14 @@ run_latehook() {
mkdir -p /new_root/blend/overlay/current/usr/bin \ mkdir -p /new_root/blend/overlay/current/usr/bin \
/new_root/blend/overlay/current/usr/sbin \ /new_root/blend/overlay/current/usr/sbin \
/new_root/blend/overlay/current/usr/share/plymouth /new_root/blend/overlay/current/usr/share
mkdir -p /new_root/usr/bin \ mkdir -p /new_root/usr/bin \
/new_root/usr/sbin \ /new_root/usr/sbin \
/new_root/usr/share/plymouth /new_root/usr/share
rm -rf /new_root/blend/overlay/workdir_1 /new_root/blend/overlay/workdir_2 /new_root/blend/overlay/workdir_3 rm -rf /new_root/blend/overlay/workdir_1 /new_root/blend/overlay/workdir_2 /new_root/blend/overlay/workdir_3
mkdir -p /new_root/blend/overlay/workdir_1 /new_root/blend/overlay/workdir_2 /new_root/blend/overlay/workdir_3 mkdir -p /new_root/blend/overlay/workdir_1 /new_root/blend/overlay/workdir_2 /new_root/blend/overlay/workdir_3
mount -t overlay overlay -o 'lowerdir=/new_root/usr/bin,upperdir=/new_root/blend/overlay/current/usr/bin,workdir=/new_root/blend/overlay/workdir_1' /new_root/usr/bin -o index=off mount -t overlay overlay -o 'lowerdir=/new_root/usr/bin,upperdir=/new_root/blend/overlay/current/usr/bin,workdir=/new_root/blend/overlay/workdir_1' /new_root/usr/bin -o index=off
mount -t overlay overlay -o 'lowerdir=/new_root/usr/sbin,upperdir=/new_root/blend/overlay/current/usr/sbin,workdir=/new_root/blend/overlay/workdir_2' /new_root/usr/sbin -o index=off mount -t overlay overlay -o 'lowerdir=/new_root/usr/sbin,upperdir=/new_root/blend/overlay/current/usr/sbin,workdir=/new_root/blend/overlay/workdir_2' /new_root/usr/sbin -o index=off
mount -t overlay overlay -o 'lowerdir=/new_root/usr/share/plymouth,upperdir=/new_root/blend/overlay/current/usr/share/plymouth,workdir=/new_root/blend/overlay/workdir_3' /new_root/usr/share/plymouth -o index=off mount -t overlay overlay -o 'lowerdir=/new_root/usr/share,upperdir=/new_root/blend/overlay/current/usr/share,workdir=/new_root/blend/overlay/workdir_3' /new_root/usr/share -o index=off
} }

View file

@ -21,6 +21,8 @@ if [ ! -f '/run/.containerenv' ]; then
exit 1 exit 1
fi fi
shopt -s extglob
while true; do while true; do
case $1 in case $1 in
--uid) --uid)
@ -74,6 +76,11 @@ cat << 'EOF'
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░
===================
Credits
===================
* NVIDIA driver support - Luca Di Maio (from Distrobox)
EOF EOF
echo echo
@ -85,7 +92,7 @@ bmount() {
! [[ -e "$2" ]] && findmnt "$2" &>/dev/null && umount "$2" # unmount target dir if a mount ! [[ -e "$2" ]] && findmnt "$2" &>/dev/null && umount "$2" # unmount target dir if a mount
[[ -d "$1" ]] && mkdir -p "$2" # create target dir if source is a dir [[ -d "$1" ]] && mkdir -p "$2" # create target dir if source is a dir
[[ -f "$1" ]] && touch "$2" # create target file if source is a file [[ -f "$1" ]] && mkdir -p "$(dirname "$2")"; touch "$2" # create target file if source is a file
mountflags="rslave" mountflags="rslave"
@ -104,10 +111,12 @@ if command -v apt-get &>/dev/null; then
diffutils findutils gnupg2 sudo time util-linux libnss-myhostname \ diffutils findutils gnupg2 sudo time util-linux libnss-myhostname \
libvte-2.9[0-9]-common libvte-common lsof ncurses-base passwd \ libvte-2.9[0-9]-common libvte-common lsof ncurses-base passwd \
pinentry-curses libegl1-mesa libgl1-mesa-glx libvulkan1 mesa-vulkan-drivers &>/dev/null pinentry-curses libegl1-mesa libgl1-mesa-glx libvulkan1 mesa-vulkan-drivers &>/dev/null
elif command -v pacman &>/dev/null; then elif command -v pacman &>/dev/null; then
pacman --noconfirm -Syyu &>/dev/null pacman --noconfirm -Syyu &>/dev/null
pacman --noconfirm -Sy bash bc curl wget diffutils findutils gnupg sudo time util-linux vte-common lsof ncurses pinentry \ pacman --noconfirm -Sy bash bc curl wget diffutils findutils gnupg sudo time util-linux vte-common lsof ncurses pinentry \
mesa opengl-driver vulkan-intel vulkan-radeon &>/dev/null mesa opengl-driver vulkan-intel vulkan-radeon base-devel git &>/dev/null
elif command -v dnf &>/dev/null; then elif command -v dnf &>/dev/null; then
dnf install -y --allowerasing bash bc curl wget diffutils findutils dnf-plugins-core gnupg2 less lsof passwd pinentry \ dnf install -y --allowerasing bash bc curl wget diffutils findutils dnf-plugins-core gnupg2 less lsof passwd pinentry \
procps-ng vte-profile ncurses util-linux sudo time shadow-utils vulkan mesa-vulkan-drivers \ procps-ng vte-profile ncurses util-linux sudo time shadow-utils vulkan mesa-vulkan-drivers \
@ -173,6 +182,62 @@ bmount "/usr/bin/host-blend" "/usr/bin/blend" ro
if [[ ! -f '/.init_blend.lock' ]]; then if [[ ! -f '/.init_blend.lock' ]]; then
#######################################################################
# NVIDIA driver integration. This is straight from https://github.com/89luca89/distrobox/blob/main/distrobox-init#L816,
# entirely thanks to an effort by Luca Di Maio, save for a few tweaks for init-blend. Thanks, in case you're reading this!
NVIDIA_FILES="$(find /run/host/usr/ \
-path "/run/host/usr/share/doc*" -prune -o \
-path "/run/host/usr/src*" -prune -o \
-path "/run/host/usr/lib*/modules*" -prune -o \
-path "/run/host/usr/share/man*" -prune -o \
-path "/run/host/usr/lib*" -prune -o \
-type f -iname "*nvidia*" -print 2</dev/null || :)"
for nvidia_file in ${NVIDIA_FILES}; do
dest_file="$(printf "%s" "${nvidia_file}" | sed 's|/run/host||g')"
bmount "${nvidia_file}" "${dest_file}" ro
done
# Then we find all the ".so" libraries, there are searched separately
# because we need to extract the relative path to mount them in the
# correct path based on the guest's setup
NVIDIA_LIBS="$(find /run/host/usr/lib* \
-iname "*nvidia*.so*" \
-o -iname "libcuda*.so*" \
-o -iname "libnvcuvid*.so*" \
-o -iname "libnvoptix*.so*" ||
:)"
for nvidia_lib in ${NVIDIA_LIBS}; do
dest_file="$(printf "%s" "${nvidia_lib}" |
sed 's|/run/host/usr/lib/x86_64-linux-gnu/||g' |
sed 's|/run/host/usr/lib64/||g' |
sed 's|/run/host/usr/lib/||g')"
# In the guest we need to adjust the destination path, so if we're on
# debian based containers, we need to target /usr/lib/x86_64-linux-gnu/
if [ -e "/usr/lib/x86_64-linux-gnu/" ]; then
bmount "${nvidia_lib}" "/usr/lib/x86_64-linux-gnu/${dest_file}" ro
# /usr/lib64 is common in rpm based distros
elif [ -e "/usr/lib64" ]; then
bmount "${nvidia_lib}" "/usr/lib64/${dest_file}" ro
# fallback to /usr/lib if none of the previous
else
bmount "${nvidia_lib}" "/usr/lib/${dest_file}" ro
fi
done
# Refresh ldconfig cache, also detect if there are empty files remaining
# and clean them.
# This could happen when upgrading drivers and changing versions.
empty_libs="$(ldconfig 2>&1 | grep -Eo "File.*is empty" | cut -d' ' -f2)"
if [ -n "${empty_libs}" ]; then
# shellcheck disable=SC2086
rm -f ${empty_libs}
fi
#######################################################################
### Section START (based on https://github.com/89luca89/distrobox/blob/main/distrobox-init#L816) ### Section START (based on https://github.com/89luca89/distrobox/blob/main/distrobox-init#L816)
if [ -d "/usr/lib/rpm/" ]; then if [ -d "/usr/lib/rpm/" ]; then
@ -210,7 +275,7 @@ elif [ -d "/usr/share/libalpm/scripts" ]; then
chmod 755 /usr/share/libalpm/scripts/*blend*.sh chmod 755 /usr/share/libalpm/scripts/*blend*.sh
for p in 00_blend_pre_hook 01_blend_post_hook.sh 02_blend_post_hook; do for p in 00_blend_pre_hook 01_blend_post_hook 02_blend_post_hook; do
when=PostTransaction when=PostTransaction
[[ -z "${p##*pre*}" ]] && when=PreTransaction [[ -z "${p##*pre*}" ]] && when=PreTransaction
@ -242,9 +307,20 @@ if ! grep -q "^${_uname}:" /etc/group; then
fi fi
useradd --uid "$_cuid" --gid "$_cgid" --shell "/bin/bash" --no-create-home --home "$_uhome" "$_uname" &>/dev/null useradd --uid "$_cuid" --gid "$_cgid" --shell "/bin/bash" --no-create-home --home "$_uhome" "$_uname" &>/dev/null
chown root /etc/sudo.conf
chown root /usr/bin/sudo
chmod 4755 /usr/bin/sudo
fi fi
touch /.init_blend.lock if [[ ! -f '/.init_blend.lock' ]] && command -v pacman &>/dev/null; then
cd /; git clone https://aur.archlinux.org/yay.git &>/dev/null; cd yay
chown -R "$_uname" . &>/log
sudo -u "$_uname" makepkg --noconfirm -si &>/dev/null
cd /; rm -rf yay
touch /.init_blend.lock
fi
echo echo
echo "Completed container setup." echo "Completed container setup."