diff options
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/app/doc.go | 355 | ||||
| -rw-r--r-- | cmd/app/main.go | 108 | ||||
| -rw-r--r-- | cmd/mbf/info.go | 4 |
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 { |
