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
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).

67
blend
View file

@ -111,7 +111,7 @@ def check_container(name):
return True
return False
def check_container_status(name):
def check_container_status(name):
return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"")
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.logfile_read = sys.stdout.buffer
logproc.expect('Completed container setup')
logproc.expect('Completed container setup.')
logproc.terminate()
def core_create_container():
@ -189,11 +189,6 @@ def core_create_container():
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()
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)
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.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)
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
You can install and submit web apps from the Web Store.
'''
epilog = f'''
@ -458,17 +407,13 @@ epilog = f'''
parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRESS,
epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
command_map = { 'install': install_blend,
'remove': remove_blend,
'enter': enter_container,
command_map = { 'enter': enter_container,
'exec': enter_container,
'create-container': core_create_container,
'remove-container': remove_container,
'list-containers': list_containers,
'start-containers': start_containers,
'sync': sync_blends,
'update': update_blends,
'search': search_blend,
'show': show_blend,
'help': 'help',
'version': 'version' }
parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS)

View file

@ -1,9 +1,17 @@
#!/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():
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')
try:
@ -22,6 +30,7 @@ def get_containers():
except:
return container_list
def list_use_container_bin():
try:
with open(os.path.expanduser('~/.config/blend/config.yaml')) as config_file:
@ -30,23 +39,25 @@ def list_use_container_bin():
except:
return []
def check_if_present(attr, desktop_str):
for l in desktop_str:
if l.startswith(attr + '='):
return True
return False
def which(bin):
results = []
for dir in os.environ.get('PATH').split(':'):
if os.path.isdir(dir):
for i in os.listdir(dir):
if os.path.basename(bin) == i:
results.append(os.path.join(dir, i))
if os.path.basename(bin) in os.listdir(dir):
results.append(os.path.join(dir, os.path.basename(bin)))
if results == []:
return None
return results
def create_container_binaries():
_binaries = []
remove_binaries = []
@ -54,17 +65,18 @@ def create_container_binaries():
for c in _list:
c = c.strip()
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/sbin -type f -printf "%P\n" 2>/dev/null;''').split('\n'):
find /usr/local/bin -type f -printf "%P\n" 2>/dev/null;''').split('\n'):
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
orig_which_out = which(os.path.basename(i))
which_out = None
if orig_which_out != None:
which_out = orig_which_out.copy()
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:
pass
if which_out == []:
@ -74,13 +86,17 @@ def create_container_binaries():
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:
f.write('#!/bin/sh\n')
f.write(f'# blend container: {i}\n')
f.write('#!/bin/bash\n')
f.write(f'# blend container: {c};{i}\n')
if os.path.basename(i) in _exceptions:
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
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'),
os.path.expanduser(f'~/.local/bin/blend_{c}/{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:
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:
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.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:
try:
@ -103,12 +123,15 @@ def create_container_binaries():
for b in os.listdir(os.path.expanduser(f'~/.local/bin/blend_bin')):
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():
_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:
c = c.strip()
@ -116,11 +139,13 @@ def create_container_applications():
orig_path = i.strip()
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}')
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:
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(c, f"sudo sed -i '/^TryExec=/d' {orig_path}")
_ = con_get_output(
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}')
f.write(contents)
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='):
if '/' in line:
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'
else:
line = line.strip()
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')
_ = 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'
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))
del _
@ -146,7 +174,9 @@ def create_container_applications():
if a.startswith('blend;'):
a = a.removeprefix('blend;')
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'):
session_dir = f'/usr/share/{type}'
@ -176,10 +206,13 @@ def create_container_sessions(type='xsessions'):
continue
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()
@ -188,15 +221,6 @@ try:
except:
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():
c = c.strip()
subprocess.call(['podman', 'start', c])
@ -207,4 +231,4 @@ while True:
create_container_binaries()
create_container_applications()
time.sleep(6)
time.sleep(15)

View file

@ -13,10 +13,10 @@
<body style="height: 100%;">
<br>
<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
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>
<button class="btn btn-outline-light shadow-none" id="system-button" onclick="page('system')">System</button>
</div>
@ -41,6 +41,10 @@
const $ = require("jquery")
if (fs.existsSync('/usr/bin/waydroid')) {
document.getElementById('android-button').classList.remove('d-none')
}
function page(page) {
switch (page) {
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() {
document.getElementById('initialize-btn').outerHTML =
'<button type="button" id="initialize-btn" onclick="init_waydroid()" class="btn btn-primary" disabled>Initializing...</button>'
let init_worker = new Worker(
`data:text/javascript,
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'])
setTimeout(() => {
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'])
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)
`
)
init_worker.onmessage = e => {
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')
}
}
}
function enable_multi_window() {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary" disabled>Enabling...</button>'
let multi_window_worker = new Worker(
function install_aurora_store() {
document.getElementById('aurora-store-btn').outerHTML =
`<button type="button" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success" disabled>Installing...</button>`
let aurora_store_worker = new Worker(
`data:text/javascript,
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown'])
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)
require('child_process').spawnSync('sh', ['-c', 'mkdir -p ~/.cache/blend-settings; rm -f ~/.cache/blend-settings/aurora.apk'])
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') {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="disable_multi_window()" class="btn btn-primary">Disable</button>'
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>`
} 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() {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary" disabled>Disabling...</button>'
let multi_window_worker = new Worker(
function install_f_droid() {
document.getElementById('f-droid-btn').outerHTML =
`<button type="button" id="f-droid-btn" onclick="install_f_droid()"
class="btn btn-primary" disabled>Installing...</button>`
let f_droid_worker = new Worker(
`data:text/javascript,
require('child_process').spawn('sh', ['-c', 'waydroid session start & disown'])
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)
require('child_process').spawnSync('sh', ['-c', 'mkdir -p ~/.cache/blend-settings; rm -f ~/.cache/blend-settings/f-droid.apk'])
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') {
document.getElementById('multiwindow-btn').outerHTML =
'<button type="button" id="multiwindow-btn" onclick="enable_multi_window()" class="btn btn-primary">Enable</button>'
document.getElementById('f-droid-btn').outerHTML =
`<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() {
let check_worker = new Worker(
`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>'
}
}
function waydroid_open_settings() {
require('child_process').spawn('waydroid', ['app', 'launch', 'com.android.settings'])
}
require('fs').stat('/var/lib/waydroid', (err, stat) => {
if (err == null) {
document.getElementById('waydroid-initialize-settings').classList.add('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', () => {
if (!document.getElementById('automatic-state-toggle').checked) {
let enable_autostate_worker = new Worker(

View file

@ -47,6 +47,8 @@ function undo_rollback() {
}
function save_state() {
$("#settings-tabs").find("*").prop('disabled', true)
let save_state_worker = new Worker(
`data:text/javascript,
let s = require('child_process').spawnSync('pkexec', ['blend-system', 'save-state']).status
@ -61,11 +63,13 @@ function save_state() {
if (e.data == 'success') {
document.getElementById('save-state-btn').outerHTML =
'<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 =
'<button type="button" id="save-state-btn" onclick="save_state()" class="btn btn-success">Save state</button>', 2000)
} else {
document.getElementById('save-state-btn').outerHTML =
'<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 =
'<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="col-12 col-lg-10 col-xl-8 mx-auto">
<div id="waydroid-initialize-settings">
<div class="list-group mt-3 mb-5 shadow" id="waydroid-initialize-settings">
<div class="list-group-item">
<div class="list-group mt-3 mb-5" id="waydroid-initialize-settings">
<div class="list-group-item shadow">
<div class="row align-items-center">
<div class="col">
<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>
<strong class="mb-0">Initialize Android app support</strong>
<p class="text-muted mb-0">You may be asked to enter your password repeatedly to initialize WayDroid.</p>
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
@ -16,57 +16,34 @@
</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 class="d-none" id="waydroid-initialized-settings">
<div id="main-list" class="mb-3">
<div class="list-group-item" id="">
<div class="row align-items-center">
<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>
<p>Android app support (WayDroid) has successfully been initialized.<span id="nvidia-warning-installed"
class="d-none"> Since you're using an <b style="color: lightgreen;">NVIDIA</b> GPU, apps will use software
rendering.</span></p>
<strong>Install a store</strong>
<div class="list-group mt-3 mb-4 shadow">
<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="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 class="list-group-item" id="multi-window">
<div class="row align-items-center">
<div class="col">
<strong class="mb-0">Aurora Store (Nightly)</strong>
<strong class="mb-0">Aurora Store</strong>
<p class="text-muted mb-0">An open-source Google Play Store client.</p>
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
<button type="button" id="aurora-store-inst-btn" onclick="install_aurora_store()"
class="btn btn-primary" disabled>Checking status...</button>
<button type="button" id="aurora-store-btn" onclick="install_aurora_store()"
class="btn btn-success" disabled>Checking status...</button>
</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="col">
<strong class="mb-0">F-Droid</strong>
@ -74,14 +51,19 @@
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
<button type="button" id="f-droid-btn" onclick="install_f_droid()" class="btn btn-primary"
disabled>Checking status...</button>
<button type="button" id="f-droid-btn" onclick="install_f_droid()"
class="btn btn-primary" disabled>Checking status...</button>
</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>

View file

@ -1,8 +1,8 @@
<div class="container-fluid d-flex justify-content-center">
<div class="col-12 col-lg-10 col-xl-8 mx-auto">
<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>
<div class="list-group mb-5 shadow" id="container-list">
<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-4 shadow" id="container-list">
<div class="list-group-item">
<div class="row align-items-center">
<div class="col">
@ -18,6 +18,7 @@
<div class="container-fluid d-flex justify-content-center">
<div class="col-12 col-lg-10 col-xl-8 mx-auto">
<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">
<div class="form-group row">
<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="col">
<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
last two).</p>
<p class="text-muted mb-0">blendOS creates copies of apps and config every 12 hours (and keeps the
previous one).</p>
</div>
<div class="col-auto">
<div class="form-check form-switch align-middle">
<input class="form-check-input align-middle" type="checkbox" role="switch"
id="automatic-state-toggle">
<input class="form-check-input align-middle" type="checkbox" role="switch" id="automatic-state-toggle">
</div>
</div>
</div>
@ -26,7 +25,8 @@
</div>
<div class="col-auto">
<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>

View file

@ -80,6 +80,10 @@
fit.fit()
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);
});

View file

@ -99,7 +99,7 @@ def autosave_state():
while True:
if not os.path.isfile('/blend/states/.disable_states'):
save_state()
time.sleep(6*60*60) # XXX: make this configurable
time.sleep(12*60*60) # XXX: make this configurable
def toggle_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}help{colors.reset} Show this help message 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}toggle-states{colors.reset} Enable/disable automatic state creation (you can still manually save states).
{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)
command_map = { 'help': 'help',
'version': 'version',
'load-overlay': load_overlay,
'save-state': save_state,
'toggle-states': toggle_states,
'autosave-state': autosave_state,

View file

@ -17,14 +17,14 @@ run_latehook() {
mkdir -p /new_root/blend/overlay/current/usr/bin \
/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 \
/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
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/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
fi
shopt -s extglob
while true; do
case $1 in
--uid)
@ -74,6 +76,11 @@ cat << 'EOF'
░ ░ ░ ░ ░ ░ ░
░ ░
===================
Credits
===================
* NVIDIA driver support - Luca Di Maio (from Distrobox)
EOF
echo
@ -85,7 +92,7 @@ bmount() {
! [[ -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
[[ -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"
@ -104,10 +111,12 @@ if command -v apt-get &>/dev/null; then
diffutils findutils gnupg2 sudo time util-linux libnss-myhostname \
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
elif command -v pacman &>/dev/null; then
pacman --noconfirm -Syyu &>/dev/null
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
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 \
@ -173,6 +182,62 @@ bmount "/usr/bin/host-blend" "/usr/bin/blend" ro
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)
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
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
[[ -z "${p##*pre*}" ]] && when=PreTransaction
@ -242,9 +307,20 @@ if ! grep -q "^${_uname}:" /etc/group; then
fi
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
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 "Completed container setup."