Compare commits

..

4 Commits

Author SHA1 Message Date
0e957cc9c1
release: 0.0.2
All checks were successful
Release / Create release (push) Successful in 43s
Test / Create distribution (push) Successful in 25s
Test / Sandbox (push) Successful in 40s
Test / Hakurei (push) Successful in 45s
Test / Sandbox (race detector) (push) Successful in 39s
Test / Planterette (push) Successful in 1m41s
Test / Hakurei (race detector) (push) Successful in 1m44s
Test / Flake checks (push) Successful in 1m14s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-06-25 21:11:11 +09:00
aa454b158f
cmd/planterette: remove hsu special case
All checks were successful
Test / Hakurei (push) Successful in 42s
Test / Create distribution (push) Successful in 25s
Test / Sandbox (push) Successful in 40s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Sandbox (race detector) (push) Successful in 38s
Test / Planterette (push) Successful in 40s
Test / Flake checks (push) Successful in 1m15s
Remove special case and invoke hakurei out of process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-06-25 20:50:24 +09:00
7007bd6a1c
workflows: port release workflow to github
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 1m55s
Test / Hakurei (push) Successful in 2m46s
Test / Sandbox (race detector) (push) Successful in 3m6s
Test / Fpkg (push) Successful in 3m31s
Test / Hakurei (race detector) (push) Successful in 4m15s
Test / Flake checks (push) Successful in 1m8s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-06-25 20:17:53 +09:00
00efc95ee7
workflows: port test workflow to github
All checks were successful
Test / Create distribution (push) Successful in 24s
Test / Sandbox (push) Successful in 1m29s
Test / Sandbox (race detector) (push) Successful in 2m54s
Test / Fpkg (push) Successful in 3m10s
Test / Flake checks (push) Successful in 1m8s
Test / Hakurei (race detector) (push) Successful in 4m10s
Test / Hakurei (push) Successful in 1m57s
This is a much less useful port of the test workflow and runs much slower due to runner limitations.

Still better than nothing though.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-06-25 19:37:45 +09:00
25 changed files with 215 additions and 113 deletions

View File

@ -73,20 +73,20 @@ jobs:
path: result/* path: result/*
retention-days: 1 retention-days: 1
fpkg: planterette:
name: Fpkg name: Planterette
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Run NixOS test - name: Run NixOS test
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.fpkg run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.planterette
- name: Upload test output - name: Upload test output
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: "fpkg-vm-output" name: "planterette-vm-output"
path: result/* path: result/*
retention-days: 1 retention-days: 1
@ -97,7 +97,7 @@ jobs:
- race - race
- sandbox - sandbox
- sandbox-race - sandbox-race
- fpkg - planterette
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout

1
.github/workflows/README vendored Normal file
View File

@ -0,0 +1 @@
This port is solely for releasing to the github mirror and serves no purpose during development.

46
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Create release
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for release
run: nix build --print-out-paths --print-build-logs .#dist
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |-
result/hakurei-**

48
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Test
on:
- push
jobs:
dist:
name: Create distribution
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for test
id: build-test
run: >-
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: "hakurei-${{ steps.build-test.outputs.rev }}"
path: result/*
retention-days: 1

View File

@ -1,38 +1,29 @@
Hakurei <p align="center">
======= <a href="https://git.gensokyo.uk/security/hakurei">
<picture>
<img src="https://basement.gensokyo.uk/images/yukari1.png" width="200px" alt="Yukari">
</picture>
</a>
</p>
[![Go Reference](https://pkg.go.dev/badge/git.gensokyo.uk/security/hakurei.svg)](https://pkg.go.dev/git.gensokyo.uk/security/hakurei) <p align="center">
[![Go Report Card](https://goreportcard.com/badge/git.gensokyo.uk/security/hakurei)](https://goreportcard.com/report/git.gensokyo.uk/security/hakurei) <a href="https://pkg.go.dev/git.gensokyo.uk/security/hakurei"><img src="https://pkg.go.dev/badge/git.gensokyo.uk/security/hakurei.svg" alt="Go Reference" /></a>
<a href="https://goreportcard.com/report/git.gensokyo.uk/security/hakurei"><img src="https://goreportcard.com/badge/git.gensokyo.uk/security/hakurei" alt="Go Report Card" /></a>
</p>
Lets you run graphical applications as dedicated subordinate users in a container environment with a nice NixOS Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
module to configure target users and provide launch scripts and desktop files. It also implements [planterette (WIP)](cmd/planterette), a self-contained Android-like package manager with modern security features.
Why would you want this? ## NixOS Module usage
- It protects the desktop environment from applications. The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md).
- It protects applications from each other.
- It provides UID isolation on top of the standard application sandbox.
If you have a flakes-enabled nix environment, you can try out the tool by running:
```shell
nix run git+https://git.gensokyo.uk/security/hakurei -- help
```
## Module usage
The NixOS module currently requires home-manager to configure subordinate users.
Full module documentation can be found [here](options.md).
To use the module, import it into your configuration with To use the module, import it into your configuration with
```nix ```nix
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
hakurei = { hakurei = {
url = "git+https://git.gensokyo.uk/security/hakurei"; url = "git+https://git.gensokyo.uk/security/hakurei";

View File

@ -1,29 +0,0 @@
package main
import (
"context"
"os"
"git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal/app"
"git.gensokyo.uk/security/hakurei/internal/app/instance"
"git.gensokyo.uk/security/hakurei/internal/hlog"
)
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
rs := new(app.RunState)
a := instance.MustNew(instance.ISetuid, ctx, std)
var code int
if sa, err := a.Seal(config); err != nil {
hlog.PrintBaseError(err, "cannot seal app:")
code = 1
} else {
code = instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs))
}
if code != 0 {
beforeFail()
os.Exit(code)
}
}

View File

@ -41,7 +41,7 @@ func main() {
log.Fatalf("cannot read parent executable path: %v", err) log.Fatalf("cannot read parent executable path: %v", err)
} else if strings.HasSuffix(p, " (deleted)") { } else if strings.HasSuffix(p, " (deleted)") {
log.Fatal("hakurei executable has been deleted") log.Fatal("hakurei executable has been deleted")
} else if p != mustCheckPath(hmain) && p != mustCheckPath(fpkg) { } else if p != mustCheckPath(hmain) {
log.Fatal("this program must be started by hakurei") log.Fatal("this program must be started by hakurei")
} else { } else {
toolPath = p toolPath = p

View File

@ -16,15 +16,8 @@ buildGoModule {
go mod init hsu >& /dev/null go mod init hsu >& /dev/null
''; '';
ldflags = ldflags = lib.attrsets.foldlAttrs (
lib.attrsets.foldlAttrs ldflags: name: value:
( ldflags ++ [ "-X main.${name}=${value}" ]
ldflags: name: value: ) [ "-s -w" ] { hmain = "${hakurei}/libexec/hakurei"; };
ldflags ++ [ "-X main.${name}=${value}" ]
)
[ "-s -w" ]
{
hmain = "${hakurei}/libexec/hakurei";
fpkg = "${hakurei}/libexec/fpkg";
};
} }

View File

@ -9,7 +9,6 @@ const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
var ( var (
hmain = compPoison hmain = compPoison
fpkg = compPoison
) )
func mustCheckPath(p string) string { func mustCheckPath(p string) string {

View File

@ -13,36 +13,23 @@ import (
"git.gensokyo.uk/security/hakurei/command" "git.gensokyo.uk/security/hakurei/command"
"git.gensokyo.uk/security/hakurei/hst" "git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal" "git.gensokyo.uk/security/hakurei/internal"
"git.gensokyo.uk/security/hakurei/internal/app/instance"
"git.gensokyo.uk/security/hakurei/internal/hlog" "git.gensokyo.uk/security/hakurei/internal/hlog"
"git.gensokyo.uk/security/hakurei/internal/sys"
"git.gensokyo.uk/security/hakurei/sandbox"
) )
const shellPath = "/run/current-system/sw/bin/bash" const shellPath = "/run/current-system/sw/bin/bash"
var ( var (
errSuccess = errors.New("success") errSuccess = errors.New("success")
std sys.State = new(sys.Std)
) )
func init() { func init() {
hlog.Prepare("fpkg") hlog.Prepare("planterette")
if err := os.Setenv("SHELL", shellPath); err != nil { if err := os.Setenv("SHELL", shellPath); err != nil {
log.Fatalf("cannot set $SHELL: %v", err) log.Fatalf("cannot set $SHELL: %v", err)
} }
} }
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE
sandbox.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallFmsg)
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
// not fatal: this program runs as the privileged user
}
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
log.Fatal("this program must not run as root") log.Fatal("this program must not run as root")
} }
@ -55,15 +42,10 @@ func main() {
flagVerbose bool flagVerbose bool
flagDropShell bool flagDropShell bool
) )
c := command.New(os.Stderr, log.Printf, "fpkg", func([]string) error { c := command.New(os.Stderr, log.Printf, "planterette", func([]string) error { internal.InstallFmsg(flagVerbose); return nil }).
internal.InstallFmsg(flagVerbose)
return nil
}).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action") Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
{ {
var ( var (
flagDropShellActivate bool flagDropShellActivate bool
@ -84,7 +66,7 @@ func main() {
} }
/* /*
Look up paths to programs started by fpkg. Look up paths to programs started by planterette.
This is done here to ease error handling as cleanup is not yet required. This is done here to ease error handling as cleanup is not yet required.
*/ */
@ -100,7 +82,7 @@ func main() {
*/ */
var workDir string var workDir string
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil { if p, err := os.MkdirTemp("", "planterette.*"); err != nil {
log.Printf("cannot create temporary directory: %v", err) log.Printf("cannot create temporary directory: %v", err)
return err return err
} else { } else {

60
cmd/planterette/proc.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
"context"
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"git.gensokyo.uk/security/hakurei/hst"
"git.gensokyo.uk/security/hakurei/internal"
"git.gensokyo.uk/security/hakurei/internal/hlog"
)
var hakureiPath = internal.MustHakureiPath()
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
var (
cmd *exec.Cmd
st io.WriteCloser
)
if r, w, err := os.Pipe(); err != nil {
beforeFail()
log.Fatalf("cannot pipe: %v", err)
} else {
if hlog.Load() {
cmd = exec.CommandContext(ctx, hakureiPath, "-v", "app", "3")
} else {
cmd = exec.CommandContext(ctx, hakureiPath, "app", "3")
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r}
st = w
}
go func() {
if err := json.NewEncoder(st).Encode(config); err != nil {
beforeFail()
log.Fatalf("cannot send configuration: %v", err)
}
}()
if err := cmd.Start(); err != nil {
beforeFail()
log.Fatalf("cannot start hakurei: %v", err)
}
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
beforeFail()
internal.Exit(exitError.ExitCode())
} else {
beforeFail()
log.Fatalf("cannot wait: %v", err)
}
}
}

View File

@ -9,7 +9,7 @@ let
buildPackage = self.buildPackage.${system}; buildPackage = self.buildPackage.${system};
in in
nixosTest { nixosTest {
name = "fpkg"; name = "planterette";
nodes.machine = { nodes.machine = {
environment.etc = { environment.etc = {
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; }; "foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };

View File

@ -79,15 +79,15 @@ print(machine.succeed("sudo -u alice -i hakurei version"))
machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/run/user/1000/wayland-1")
machine.wait_for_file("/tmp/sway-ipc.sock") machine.wait_for_file("/tmp/sway-ipc.sock")
# Prepare fpkg directory: # Prepare planterette directory:
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000") machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
# Install fpkg app: # Install planterette app:
swaymsg("exec fpkg -v install /etc/foot.pkg && touch /tmp/fpkg-install-done") swaymsg("exec planterette -v install /etc/foot.pkg && touch /tmp/planterette-install-ok")
machine.wait_for_file("/tmp/fpkg-install-done") machine.wait_for_file("/tmp/planterette-install-ok")
# Start app (foot) with Wayland enablement: # Start app (foot) with Wayland enablement:
swaymsg("exec fpkg -v start org.codeberg.dnkl.foot") swaymsg("exec planterette -v start org.codeberg.dnkl.foot")
wait_for_window("hakurei@machine-foot") wait_for_window("hakurei@machine-foot")
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n") machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client") machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client")

2
dist/install.sh vendored
View File

@ -2,7 +2,7 @@
cd "$(dirname -- "$0")" || exit 1 cd "$(dirname -- "$0")" || exit 1
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei" install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
install -vDm0755 "bin/fpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/fpkg" install -vDm0755 "bin/planterette" "${HAKUREI_INSTALL_PREFIX}/usr/bin/planterette"
install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu" install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then

4
dist/release.sh vendored
View File

@ -11,9 +11,9 @@ cp -rv "dist/comp" "${out}"
go generate ./... go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static' go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
-X git.gensokyo.uk/security/hakurei/internal.version=${VERSION} -X git.gensokyo.uk/security/hakurei/internal.version=${VERSION}
-X git.gensokyo.uk/security/hakurei/internal.hakurei=/usr/bin/hakurei
-X git.gensokyo.uk/security/hakurei/internal.hsu=/usr/bin/hsu -X git.gensokyo.uk/security/hakurei/internal.hsu=/usr/bin/hsu
-X main.hmain=/usr/bin/hakurei -X main.hmain=/usr/bin/hakurei" ./...
-X main.fpkg=/usr/bin/fpkg" ./...
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}" rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
rm -rf "./${out}" rm -rf "./${out}"

View File

@ -32,7 +32,7 @@
buildPackage = forAllSystems ( buildPackage = forAllSystems (
system: system:
nixpkgsFor.${system}.callPackage ( nixpkgsFor.${system}.callPackage (
import ./cmd/fpkg/build.nix { import ./cmd/planterette/build.nix {
inherit inherit
nixpkgsFor nixpkgsFor
system system
@ -69,7 +69,7 @@
withRace = true; withRace = true;
}; };
fpkg = callPackage ./cmd/fpkg/test { inherit system self; }; planterette = callPackage ./cmd/planterette/test { inherit system self; };
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } '' formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
cd ${./.} cd ${./.}
@ -125,7 +125,7 @@
glibc glibc
xdg-dbus-proxy xdg-dbus-proxy
# fpkg # planterette
zstd zstd
gnutar gnutar
coreutils coreutils

View File

@ -8,16 +8,26 @@ import (
) )
var ( var (
hsu = compPoison hakurei = compPoison
hsu = compPoison
) )
func MustHakureiPath() string {
if name, ok := checkPath(hakurei); ok {
return name
}
hlog.BeforeExit()
log.Fatal("invalid hakurei path, this program is compiled incorrectly")
return compPoison // unreachable
}
func MustHsuPath() string { func MustHsuPath() string {
if name, ok := checkPath(hsu); ok { if name, ok := checkPath(hsu); ok {
return name return name
} }
hlog.BeforeExit() hlog.BeforeExit()
log.Fatal("invalid hsu path, this program is compiled incorrectly") log.Fatal("invalid hsu path, this program is compiled incorrectly")
return compPoison return compPoison // unreachable
} }
func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) } func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) }

View File

@ -35,7 +35,7 @@ package
*Default:* *Default:*
` <derivation hakurei-static-x86_64-unknown-linux-musl-0.4.1> ` ` <derivation hakurei-static-x86_64-unknown-linux-musl-0.0.2> `
@ -916,7 +916,7 @@ package
*Default:* *Default:*
` <derivation hakurei-hsu-0.4.1> ` ` <derivation hakurei-hsu-0.0.2> `

View File

@ -13,7 +13,7 @@
wayland-scanner, wayland-scanner,
xorg, xorg,
# for fpkg # for planterette
zstd, zstd,
gnutar, gnutar,
coreutils, coreutils,
@ -31,7 +31,7 @@
buildGoModule rec { buildGoModule rec {
pname = "hakurei"; pname = "hakurei";
version = "0.0.1"; version = "0.0.2";
srcFiltered = builtins.path { srcFiltered = builtins.path {
name = "${pname}-src"; name = "${pname}-src";
@ -76,6 +76,7 @@ buildGoModule rec {
) )
{ {
version = "v${version}"; version = "v${version}";
hakurei = "${placeholder "out"}/libexec/hakurei";
hsu = "/run/wrappers/bin/hsu"; hsu = "/run/wrappers/bin/hsu";
}; };
@ -116,7 +117,7 @@ buildGoModule rec {
makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \ makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages} --inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
makeBinaryWrapper "$out/libexec/fpkg" "$out/bin/fpkg" \ makeBinaryWrapper "$out/libexec/planterette" "$out/bin/planterette" \
--inherit-argv0 --prefix PATH : ${ --inherit-argv0 --prefix PATH : ${
lib.makeBinPath ( lib.makeBinPath (
appPackages appPackages