Example: Plaintext
For example, let’s manually install Plaintext, rather than relying on Outer Shell to do it for us.
Plaintext is a more complete example:
- It listens on a Unix socket, not a TCP port.
- The service manager owns the socket and starts the backend on first input.
- It registers itself as an opener for
public.text.
The paths below assume you already have the Plaintext backend executable and content bundles installed somewhere. Adjust them to match your install location.
Linux
Create ~/.config/systemd/user/org.outershell.Plaintext.service:
[Unit]
Description=Plaintext
After=network.target
[Service]
Type=simple
WorkingDirectory=%h/.local/share/outershell/apps/org.outershell.Plaintext
Environment=OUTERSHELLD_API_SOCKET=%t/outershelld-api
ExecStart=%h/.local/share/outershell/apps/org.outershell.Plaintext/PlaintextBackend \
--label org.outershell.Plaintext \
--socket-path %t/org.outershell.Plaintext \
--bundles-dir %h/.local/share/outershell/apps/org.outershell.Plaintext/bundles
Restart=on-failure
[Install]
WantedBy=default.target
Create ~/.config/systemd/user/org.outershell.Plaintext.socket:
[Unit]
Description=Plaintext Socket
[Socket]
ListenStream=%t/org.outershell.Plaintext
FileDescriptorName=http
SocketMode=0600
[Install]
WantedBy=sockets.target
Enable the socket:
systemctl --user daemon-reload
systemctl --user enable --now org.outershell.Plaintext.socket
Register the backend and app endpoint:
SOCKET_PATH="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/org.outershell.Plaintext"
outerctl backend upsert \
--backend org.outershell.Plaintext \
--name Plaintext \
--systemd-unit org.outershell.Plaintext.service
outerctl app upsert \
--backend org.outershell.Plaintext \
--name Plaintext \
--socket-path "$SOCKET_PATH" \
--path /
Register the file association:
outerctl opener upsert \
--backend org.outershell.Plaintext \
--content-type public.text \
--url-template "?file={file}" \
--rank 0 \
--capabilities view,edit
public.text is a built-in content type, so you do not need to create it with
outerctl content-type add.
macOS
On macOS, launchd owns the socket via the plist’s Sockets dictionary. The
backend calls launch_activate_socket("Listener", ...) to receive the listener
that launchd created.
First find the per-user temporary directory. This is where Outer Shell expects user-level Unix sockets:
SOCKET_PATH="$(getconf DARWIN_USER_TEMP_DIR)org.outershell.Plaintext"
Create ~/Library/LaunchAgents/org.outershell.Plaintext.plist.
Replace /Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext
with your install location:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.outershell.Plaintext</string>
<key>ProgramArguments</key>
<array>
<string>/Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext/PlaintextBackend</string>
<string>--label</string>
<string>org.outershell.Plaintext</string>
<string>--socket-path</string>
<string>/var/folders/.../T/org.outershell.Plaintext</string>
<string>--launchd-socket-name</string>
<string>Listener</string>
<string>--bundles-dir</string>
<string>/Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext/bundles</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>OUTERSHELLD_API_SOCKET</key>
<string>/var/folders/.../T/outershelld-api</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext</string>
<key>StandardOutPath</key>
<string>/Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext/backend.log</string>
<key>StandardErrorPath</key>
<string>/Users/example/Library/Application Support/outershell/apps/org.outershell.Plaintext/backend.log</string>
<key>Sockets</key>
<dict>
<key>Listener</key>
<dict>
<key>SockPathName</key>
<string>/var/folders/.../T/org.outershell.Plaintext</string>
<key>SockPathMode</key>
<integer>600</integer>
</dict>
</dict>
</dict>
</plist>
The /var/folders/.../T/ strings should be the value from
getconf DARWIN_USER_TEMP_DIR. The socket path in ProgramArguments and
Sockets must match.
Bootstrap the plist:
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/org.outershell.Plaintext.plist"
Register the backend and app endpoint:
outerctl backend upsert \
--backend org.outershell.Plaintext \
--name Plaintext \
--launchd-plist "$HOME/Library/LaunchAgents/org.outershell.Plaintext.plist"
outerctl app upsert \
--backend org.outershell.Plaintext \
--name Plaintext \
--socket-path "$SOCKET_PATH" \
--path /
Register the file association:
outerctl opener upsert \
--backend org.outershell.Plaintext \
--content-type public.text \
--url-template "?file={file}" \
--rank 0 \
--capabilities view,edit
Again, public.text is built in. The opener registration is the part that tells
Outer Shell that Plaintext can appear as an app for text files.