aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/app/doc.go355
-rw-r--r--cmd/app/main.go108
-rw-r--r--cmd/mbf/info.go4
3 files changed, 461 insertions, 6 deletions
diff --git a/cmd/app/doc.go b/cmd/app/doc.go
new file mode 100644
index 00000000..c25eca6d
--- /dev/null
+++ b/cmd/app/doc.go
@@ -0,0 +1,355 @@
+/*
+The app program is a proof-of-concept frontend for cmd/hakurei.
+
+This program is not covered by the compatibility promise. The command line
+interface and configuration syntax may change at any time.
+
+# Installation
+
+Compile cmd/app with these arguments:
+
+ go build -trimpath \
+ -ldflags='-s -w
+ -buildid=
+ -X hakurei.app/internal/info.hsuPath=/usr/local/bin/hsu' \
+ ./cmd/app
+
+Replace /usr/local/bin/hsu with the hakurei installation's absolute hsu
+pathname.
+
+# Sharing files
+
+Sharing files between apps is only possible with the permissionless shared
+filesystem ([cmd/sharefs]), as apps generally do not share credentials. It can
+be built without any special linker flags. Create a symlink at
+/usr/bin/mount.fuse.sharefs pointing to the sharefs binary when mounting sharefs
+via fstab:
+
+ sharefs /sdcard fuse.sharefs rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 0 0
+
+Replace /var/lib/sdcard with the location of the backing directory. User and
+group id must be specified in numerical form, it must own the backing directory.
+The mount point does not have to be /sdcard.
+
+# General setup
+
+The entire directory structure containing persistent app data must be created
+manually for now, due to the different ownership requirements being impossible
+to set up directly through cmd/hsu.
+
+The environment variable ROSA_APP_PATH should be set to the absolute pathname of
+the persistent state directory. This document will use $ROSA_APP_PATH to refer
+to the persistent state directory. The $ROSA_APP_PATH directory should be owned
+by the user declared in /etc/hsurc and invoking cmd/app, and must be readable
+and executable by all subordinate users. This can be done by making it
+world-readable and executable, or by adding a group directive to the common
+file.
+
+The $ROSA_APP_PATH/app directory contains app configuration files, named after
+their reverse-DNS style application identifier string. It should be owned by
+the user invoking cmd/app.
+
+The $ROSA_APP_PATH/initial directory contains the read-only base of every
+app template. Everything within it, including the directory itself, should be
+owned by the reserved user, its user and group id obtained by the command:
+
+ app id
+
+This document assumes a [Void Linux rootfs tarball] is unpacked here. It is
+possible to use other Linux distributions; Void linux is selected here because
+its package manager works correctly out of the box in the hakurei container
+environment. If the use case does not require glibc, [Alpine Linux] or
+[Chimera Linux] might be more suitable.
+
+Once the tarball is unpacked at $ROSA_APP_PATH/initial, create a directory named
+chronos in $ROSA_APP_PATH/initial/home, and directories block, bus, class, dev,
+and devices in $ROSA_APP_PATH/initial/sys. Then, recursively change ownerships
+of files and directories in $ROSA_APP_PATH/initial to the reserved user/group.
+
+The $ROSA_APP_PATH/lock directory contains lock files guarding entry into
+mutable and app containers of every template. It must be owned by the user
+invoking cmd/app.
+
+The $ROSA_APP_PATH/state directory contains app home directories, named after
+their reverse-DNS style application identifier string. The directory itself
+should be owned by the user invoking cmd/app; each directory inside must be
+owned by the subordinate user/group id of its app, obtained by the command:
+
+ app id "$APPID"
+
+where "$APPID" is the second component of the first line in the configuration
+file of the corresponding app. It is considered good practice, but not required,
+for these directory to have the permission bits set to 0700.
+
+The $ROSA_APP_PATH/template directory contains templates that apps are based
+on, which are all derived from the initial layer. The directory itself
+should be owned by the user invoking cmd/app, and each directory inside should
+be owned by the reserved user/group. Their names are used in the first component
+of the first line in the configuration file of each app, to specify that the
+file is derived from that template.
+
+The $ROSA_APP_PATH/work directory contains overlay work directories for mutable
+containers. It must contain one manually created, empty directory for each
+template, named after the template directory itself, owned by the reserved
+user/group. The directory itself and its contents should have the permission
+bits set to 0700.
+
+# Configuring the base template
+
+This section describes basic setup required for the typical desktop use case.
+Generally, multiple templates are created to mitigate the global nature of
+conventional package managers. The setup described in this section applies to
+all templates. For convenience, a "base" template should be created, containing
+this setup, and all future templates should be copied from the base template.
+To reduce disk space used, [ZFS snapshots] or an equivalent can be used. This
+section assumes the template is named "base".
+
+Create an empty directory at $ROSA_APP_PATH/template/base, owned by the reserved
+user, with permission bits set to 0755. Create its corresponding work directory
+at $ROSA_APP_PATH/work/base, also owned by the reserved user, with permission
+bits set to 0700. Once this is complete, enter its mutable container with the
+command:
+
+ app enter --shell=/bin/sh base
+
+This command overrides the container configuration to use the shell program at
+/bin/sh. Normally, cmd/app uses zsh, which is not yet installed.
+
+Once in the container, populate /etc/resolv.conf with some well-known DNS
+service, for example:
+
+ nameserver 8.8.8.8
+ nameserver 8.8.4.4
+
+The mutable container does not bind the host /etc/resolv.conf. It is generally
+recommended to populate it with a well-known service here to avoid trouble in
+future template changes. A later section configures regular app containers to
+use host configuration.
+
+After populating nameserver configuration, install zsh using the package manager
+provided by the distribution unpacked earlier. On Void Linux, this is done by
+the command:
+
+ xbps-install zsh
+
+Wait for the package installation to complete, configure zsh if necessary, then
+exit from the shell, and re-enter the container without overriding the login
+shell:
+
+ app enter base
+
+Some packages are generally required in the container for the typical desktop
+use case: xdg-user-dirs to reconfigure "well known" user directories to point
+to the inner sharefs mount point, mesa to interact with the GPU, dconf to
+configure GTK, xdg-desktop-portal-gtk to have gtk talk to dconf. Fonts generally
+need to be installed as well. Additionally, a text editor can be installed for
+editing configuration files later on. On Void Linux, these packages are
+installed by the command:
+
+ xbps-install \
+ vim \
+ mesa \
+ mesa-dri \
+ mesa-intel-dri \
+ mesa-vaapi \
+ mesa-vulkan-intel \
+ mesa-vulkan-radeon \
+ mesa-vulkan-overlay-layer \
+ dconf \
+ xdg-user-dirs \
+ xdg-desktop-portal-gtk \
+ noto-fonts-ttf \
+ noto-fonts-ttf-variable \
+ noto-fonts-ttf-extra \
+ noto-fonts-emoji \
+ noto-fonts-cjk \
+ noto-fonts-cjk-variable \
+ noto-fonts-cjk-sans \
+ noto-fonts-cjk-sans-variable \
+ noto-fonts-cjk-serif \
+ noto-fonts-cjk-serif-variable
+
+Add -32bit variants of mesa packages if multilib support is required. Adjust
+the package selection based on use case.
+
+Edit /etc/xdg/user-dirs.defaults and point directories to the inner sharefs
+mount point as required. The resulting file should look like this:
+
+ # Default settings for user directories
+ #
+ # The values are relative pathnames from the home directory and
+ # will be translated on a per-path-element basis into the users locale
+ DESKTOP=../../sdcard/Desktop
+ DOWNLOAD=../../sdcard/Download
+ TEMPLATES=../../sdcard/Templates
+ PUBLICSHARE=../../sdcard/Public
+ DOCUMENTS=../../sdcard/Documents
+ MUSIC=../../sdcard/Music
+ PICTURES=../../sdcard/Pictures
+ VIDEOS=../../sdcard/Movies
+ PROJECTS=../../sdcard/Projects
+ # Another alternative is:
+ #MUSIC=Documents/Music
+ #PICTURES=Documents/Pictures
+ #VIDEOS=Documents/Videos
+
+If this is configured, it must be applied to the home directory of each app
+individually. This can be done for all apps via the shell expression:
+
+ app run | xargs -n 1 echo app run --command=xdg-user-dirs-update
+
+This must also be done for every newly created app.
+
+The /etc/dconf directory can be set up to provide defaults across all apps. To
+do so, edit /etc/dconf/profile/user:
+
+ user-db:user
+ system-db:local
+ system-db:site
+ system-db:distro
+
+Then, place files in /etc/dconf/db/local.d containing dconf configuration. For
+example:
+
+ [org/gnome/desktop/interface]
+ gtk-enable-primary-paste=true
+ color-scheme='prefer-dark'
+ gtk-theme='adw-gtk3-dark'
+ icon-theme='Papirus-Dark'
+
+Themes must be installed in the container. Change colour-scheme and themes
+accordingly. Setting gtk-enable-primary-paste restores clipboard behaviour that
+GNOME maintainers decided to break.
+
+After editing these configuration files, update dconf system databases:
+
+ dconf update
+
+# Common configuration
+
+The optional $ROSA_APP_PATH/common file contains common configuration included
+after the specific configuration of each app. Some bind mounts are required for
+almost every graphical program, and many widely used libraries are configured
+through the environment. For most setups, these directives are generally
+required:
+
+ ; for libudev
+ ro "/sys/block"
+ ro "/sys/bus"
+ ro "/sys/class"
+ ro "/sys/dev"
+ ro "/sys/devices"
+
+ env EDITOR=vim
+ ; for apps to play nice with xdg-dbus-proxy
+ ro+ "/etc/machine-id"
+ ; template must have a /etc/resolv.conf file
+ ro+ "/etc/resolv.conf"
+ ; group name of the sharefs group configured on host
+ group media_rw
+ ; for sharefs
+ rw "/sdcard"
+ ; refer to /usr/share/zoneinfo
+ env TZ=Asia/Tokyo
+
+ ; must be installed first
+ env XCURSOR_THEME=volantes_cursors
+ ; must be generated first if using glibc
+ env LANG=en_GB.UTF-8
+ env LC_COLLATE=C
+
+# Installing an app
+
+An app is defined by a configuration file in $ROSA_APP_PATH/app, and a
+persistent state directory in $ROSA_APP_PATH/state. Before creating an app, its
+identity must be decided. Different apps should generally not share an identity.
+The next unused identity can be found using the command:
+
+ app next
+
+If the -v argument is added before the "next" command, cmd/app will additionally
+show apps sharing the same identity.
+
+Once the identity is decided, the subordinate user/group id can be obtained by
+the command:
+
+ app id "$APPID"
+
+where "$APPID" is the identity of this app. A directory must be created under
+$ROSA_APP_PATH/state, owned by this user and group id. Its permission bits
+should be set to 0700. A configuration file with the same name, owned by the
+user invoking cmd/app, must be placed in $ROSA_APP_PATH/app. Its contents are
+described in the next section. The reverse-DNS style application identifier
+string is submitted to the Wayland display server, and used as part of the
+dbus preset if enabled, so the correct identifier must be obtained. If no such
+identifier exist, use the domain of the app home page.
+
+# Configuring an app
+
+The configuration file starts with two structural directives, and the remaining
+lines are freestanding directives applied in order.
+
+The first structural directive is a line containing two components separated by
+the ':' byte. The first component is the template used by the app. the second
+component is the decimal representation of the app identity. The second
+structural directive is a shell expression passed to the shell serving as the
+initial process of the app.
+
+After the structural directives, each line contains exactly one directive or
+comment. Comment lines begin with a ';' byte: these lines are not interpreted by
+cmd/app in any way.
+
+The following section documents currently available freestanding directives.
+
+# Directives
+
+ interactive start initial process as an interactive shell
+ gpu expose GPU devices to the container
+ system_bus enable system bus in the dbus proxy
+
+ wayland expose a Wayland pathname socket via security-context-v1
+ x11 expose the X11 pathname socket
+ dbus enable the per-container xdg-dbus-proxy daemon
+ pipewire expose a pipewire pathname socket via SecurityContext
+
+ multiarch unblock system calls required for multiarch to work on
+ multiarch-enabled targets (amd64, arm64)
+ devel unblock ptrace and friends
+ userns unblock userns creation and container setup syscalls
+ net enable network access
+ abstract enable access to external abstract unix sockets
+ tty unblock dangerous terminal I/O (faking input)
+ mapuid map the target user id to the user id of the user
+ invoking cmd/app in the container user namespace
+ device mount /dev/ from the init mount namespace as is in the
+ container mount namespace
+
+ share_runtime share XDG_RUNTIME_DIR between containers under the same
+ identity
+ share_tmpdir share TMPDIR between containers under the same identity
+
+ username <name> set username of the emulated user
+ hostname <name> set container hostname
+ env KEY=VALUE set an environment variable for the initial process
+
+ ro "pathname" make a host path available to the container; the string
+ must be presented in Go string literal syntax, to
+ specify a different inner pathname, end the outer
+ pathname with NUL and specify the inner pathname after
+ the NUL byte
+ rw "pathname" like ro, but the resulting mount entry is made writable
+ ro+ "pathname" like ro, but is skipped if pathname does not exist
+ rw+ "pathname" like ro+, but the resulting mount entry is made writable
+
+ own name add an own policy for the dbus proxy
+ own_system name like own, but for the system bus if enabled
+ talk name add a talk policy for the dbus proxy
+ talk_system name like talk, but for the system bus if enabled
+
+[cmd/sharefs]: https://pkg.go.dev/hakurei.app/cmd/sharefs
+[Void Linux rootfs tarball]: https://voidlinux.org/download
+[Alpine Linux]: https://alpinelinux.org
+[Chimera Linux]: https://chimera-linux.org
+[ZFS snapshots]: https://openzfs.github.io/openzfs-docs/man/v2.4/8/zfs-snapshot.8.html
+*/
+package main
diff --git a/cmd/app/main.go b/cmd/app/main.go
index 00bceb57..5f796cfc 100644
--- a/cmd/app/main.go
+++ b/cmd/app/main.go
@@ -1,24 +1,25 @@
-// The app program is a proof-of-concept frontend for cmd/hakurei.
-//
-// This program is not covered by the compatibility promise. The command line
-// interface and configuration syntax may change at any time.
package main
import (
"context"
"errors"
+ "fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
+ "slices"
+ "strconv"
+ "strings"
"syscall"
"hakurei.app/check"
"hakurei.app/command"
"hakurei.app/fhs"
"hakurei.app/hst"
+ "hakurei.app/internal/outcome"
"hakurei.app/message"
)
@@ -200,6 +201,105 @@ func main() {
)
}
+ c.NewCommand(
+ "id", "Show user/group id of the specified appid",
+ func(args []string) error {
+ var appid int
+ switch len(args) {
+ case 0:
+ log.Println("appid not specified, assuming reserved user")
+ break
+
+ case 1:
+ var err error
+ appid, err = strconv.Atoi(args[0])
+ if err != nil {
+ return os.ErrInvalid
+ }
+ break
+
+ default:
+ return errors.New("id requires 1 argument")
+ }
+
+ fmt.Println(hst.ToUser(outcome.Info().User, appid))
+ return nil
+ },
+ )
+
+ c.NewCommand(
+ "next", "Find next unused identity",
+ func([]string) error {
+ var names []string
+ if dents, err := os.ReadDir(base.Append("app").String()); err != nil {
+ return err
+ } else {
+ names = make([]string, 0, len(dents))
+ for _, dent := range dents {
+ name := dent.Name()
+ if dent.IsDir() || (len(name) > 0 && name[0] == '.') {
+ continue
+ }
+ names = append(names, name)
+ }
+ }
+
+ apps := make([]*hst.Config, len(names))
+ for i, name := range names {
+ r, err := os.Open(base.Append("app", name).String())
+ if err != nil {
+ return err
+ }
+
+ apps[i], err = parse(name, base, r, nil)
+ if closeErr := r.Close(); err == nil {
+ err = closeErr
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ p := make(map[int][]*hst.Config)
+ for _, config := range apps {
+ p[config.Identity] = append(p[config.Identity], config)
+ }
+
+ identities := make([]int, 0, len(p))
+ for identity, a := range p {
+ identities = append(identities, identity)
+ if msg.IsVerbose() && len(a) != 1 {
+ ids := make([]string, len(a))
+ for i, config := range a {
+ ids[i] = config.ID
+ }
+ slices.Sort(ids)
+ msg.Verbosef(
+ "%s shares identity %d",
+ strings.Join(ids, ", "), identity,
+ )
+ }
+ }
+
+ if len(identities) == 0 {
+ // 0 is the reserved identity
+ fmt.Println(1)
+ return nil
+ }
+
+ slices.Sort(identities)
+ next := identities[0] - 1
+ for _, identity := range identities {
+ if identity != next+1 {
+ break
+ }
+ next = identity
+ }
+ fmt.Println(next + 1)
+ return nil
+ },
+ )
+
c.MustParse(os.Args[1:], func(err error) {
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
os.Exit(e.ExitCode())
diff --git a/cmd/mbf/info.go b/cmd/mbf/info.go
index a4bc7c02..d8691f60 100644
--- a/cmd/mbf/info.go
+++ b/cmd/mbf/info.go
@@ -56,9 +56,9 @@ func commandInfo(
mustPrintln("website : " +
strings.TrimSuffix(meta.Website, "/"))
}
- if len(meta.Dependencies) > 0 {
+ if len(meta.Runtimes) > 0 {
mustPrint("depends on :")
- for _, d := range meta.Dependencies {
+ for _, d := range meta.Runtimes {
_meta, _ := rosa.MustLoad(d)
s := _meta.Name
if _meta.Version != rosa.Unversioned {