WolOS
Reference contents

Reference · Apps and the UI kit

Scripts: sh, bash and Ruby

Check and run a shell or Ruby script the same way, as you, through the same approval gate as every helper.

On this page

What it is

A script is a text file you edit and run. WolOS treats a Ruby script exactly as a shell script: wol script check parses it without running it and lints it, wol script run runs it as you.

Why

Crystal compiles, but a script should run the moment you save it. One runner for every script language means one set of rules, one approval gate for changes to the system, and one audit log.

How

  1. Check: wol script check backup.rb. You see one line that says the script parses and the linter is clean, or one sentence per problem.
  2. Run: wol script run backup.rb [args]. It runs as you, never as root.
  3. List what is supported: wol script prints the table of kinds.

If something goes wrong

What you seeWhat to do
sudo wol script run is refusedScripts never run as root. Changes to the system go through wol, which asks.
No lint resultThe linters come with the Developer module: wol modules install dev.
A change asks for approvalA script that calls wol goes through the same gate as any helper.

Assistants and automation · Decision: Ruby scripts run like shell scripts · The wol command

Reference

Decision: ADR 0041. Code: crates/controller/src/script.rs (wol script, the KINDS table), crates/controller/src/verbs.rs (as_script, the helper gate), crates/core/src/automation/audit.rs (the audit log). Rules: docs/RULES-ruby.md (RB-nnn) beside docs/RULES-system.md (RS-nnn). Tests: crates/controller/tests/script.rs (every case for .sh and .rb), crates/controller/tests/rules_ruby.rs, crates/proxy/tests/script_entry.rs, crates/core/tests/widget_ruby.rs.

What a script is

A script is a text file you edit and run: its first line names the interpreter (#!/bin/sh, #!/usr/bin/env bash, #!/usr/bin/ruby, #!/usr/bin/env ruby), or, without that line, its name ends in .sh, .bash or .rb. WolOS treats a Ruby script exactly as it treats a shell script. Why Ruby: Crystal is the language of apps and tools (ADR 0036), but it compiles; a script should run the moment you save it. The interpreter (ruby) is part of the system; the linter (rubocop) comes with the Developer module.

KindNameInterpreterChecked withLinter (when installed)
sh.sh/bin/shsh -nshellcheck -S error -s sh
bash.bash/bin/bashbash -nshellcheck -S error -s bash
ruby.rb/usr/bin/rubyruby -wcrubocop with the WolOS cop list

wol script (or wol script --json) prints this table.

Check a script

wol script check <file> [--json] parses the file without running any of it, then runs the linter if it is installed. Each problem is one sentence; the tools' own messages (line and column) go to the terminal above it. Exit 0 when clean, 65 when not. Helpers may call it (Read).

$ wol script check backup.rb
backup.rb: ruby script, parses and rubocop is clean

Run a script

wol script run <file> [args...] runs it the same way whatever its language:

  • As you, never as root. sudo wol script run is refused.
  • An argv, never a shell string. The interpreter gets the file and your arguments as they are. Put -- before arguments that start with -- (wol script run tidy.rb -- --dry-run).
  • A clean environment. The script keeps who you are and where (HOME, USER, LANG, the XDG_* folders, the terminal, the desktop session) and gets a fixed PATH (/usr/local/bin:/usr/bin). Everything else from your shell, such as tokens and keys, is left out.
  • The helper gate for every change. The script runs as the helper script:<file name> (WOL_HELPER): when it calls wol to change something, you see the approval sheet; an administrator change asks for the password; your helper settings apply (docs/modules/automation.md). Reads run without asking.
  • One audit record per run, in Settings > Assistants & Automation: which script, when, the exit code. The script's arguments are not recorded, since they may hold secrets.

The exit code is the script's own.

Run a script on another computer

wol script on <host> <file> [args...] sends the file over ssh on stdin and runs it there the same way: clean environment, WOL_HELPER set, the interpreter of the other computer (/usr/bin/ruby - or /bin/sh -s --). Nothing is copied to the other disk. host is a name, an address or user@host from your ssh setup; ssh never asks for a password here (keys only, BatchMode). On another WolOS the gate is the other computer's: an ssh session has no desktop showing, so a change through wol is refused with "No WolOS session is showing", and reads work. The other computer needs ssh turned on (Sharing) and the interpreter (every WolOS has sh and ruby).

What the sandbox is, and is not

The runner removes the easy ways to go wrong: no root, no shell parsing of your arguments, no secrets from your environment, every wol change asked on screen, every run recorded. It is not a file jail: a script can read and write what you can, like any program you start. The workspace jail (wol jail run, ADR 0032) is planned and will wrap wol script run when it lands.

Scripts in other places

The same file works everywhere a program does, with nothing to declare:

PlaceHowGate
Widget data source (command)argv, never a shell; nice 10, CPU limit, output cap (wol_core::widgets::run_limited)run permission
Plugin command, hotkey, toggleargvrun permission
.wol bundle entry (Linux)the entry itself, by its shebang (proxy::plan)the bundle's
A helper of your ownset WOL_HELPER=<name> or start it with wol script runhelper gate

Writing a Ruby script for WolOS

Start from this; it passes wol script check and the RB rules:

#!/usr/bin/env ruby # SPDX-License-Identifier: GPL-2.0-or-later # Turns Do Not Disturb on while a backup runs. Why: notifications during a backup are noise. # How: wol script run backup.rb <folder> # frozen_string_literal: true

require 'json' require 'open3'

folder = ARGV.fetch(0) system('wol', 'focus', 'on', exception: true) out, status = Open3.capture2('wol', 'sys', 'disks', '--json') warn 'disk list unavailable' unless status.success? puts JSON.parse(out).length if status.success? system('tar', 'czf', "#{folder}.tar.gz", folder, exception: true)

Rules worth knowing (all in docs/RULES-ruby.md): commands as separate arguments (system('tar', 'czf', out, dir), never system("tar czf #{out}") or backticks); JSON.parse, never Marshal.load or YAML.load; only the interpreter's own libraries (STDLIB), no gems; Dir.mktmpdir with a block; timeouts on Net::HTTP. Scripts WolOS ships use #!/usr/bin/ruby; root helpers stay sh.

From the developer documentation, wonderlandos/wolos docs/modules/scripting.md.