Skip to content
 
 

Repository files navigation

ScreenGuard

Parental-control screen-time manager for Linux. A lightweight server+agent system that enforces daily screen-time limits and schedules on managed machines.

Features

  • Daily time limits and weekly schedules per user profile
  • Hard enforcement — locks all graphical sessions when time runs out
  • Optional session preservation keeps applications and unsaved work running behind the lock screen
  • Desktop notifications: warns at 15 / 10 / 5 / 1 minute before lockout
  • Admin can send arbitrary text messages to any managed user's desktop
  • Time adjustments (add or remove minutes) with optional reason shown to the user
  • mDNS auto-discovery — agents find the server on the local network without manual configuration
  • Web UI for administration
  • Static binaries, no runtime dependencies

Mobile app

A Flutter Android app lives in the screenguard-mobile repository. It lets you manage profiles, devices, schedules, and daily limits from your phone — no browser needed.

Download: grab screenguard-android-<version>.apk from the latest mobile release and install it (enable Install unknown apps in Android settings first).

The app connects directly to the same REST API as the web UI. No extra setup needed on the server.

Profiles dashboard    Profile detail with usage chart    Devices list

See the screenguard-mobile repo for full details and build instructions.

Architecture

┌─────────────────────────────────┐        ┌──────────────────────────┐
│  Server machine                 │        │  Managed machine (child) │
│                                 │        │                          │
│  screenguard-server  (REST API) │◄──WS───│  screenguard-agent       │
│  screenguard web UI  (Flask)    │        │  (enforces limits,       │
│                                 │        │   sends notifications)   │
└─────────────────────────────────┘        └──────────────────────────┘
         ▲
         │ REST
    Android app
 (screenguard-mobile)

The server and agent can run on the same machine or on separate machines. The agent connects to the server over a persistent WebSocket connection. mDNS (Avahi/Bonjour) is used for automatic discovery on the local network.

Requirements

  • Linux with systemd
  • x86_64 or aarch64 CPU
  • Managed machines: a graphical session manager that supports loginctl terminate-session (GNOME, KDE, etc.)
  • Server machine: any Linux with systemd; does not need a graphical desktop

Install

curl -fsSL https://github.com/adambie/screenguard/releases/latest/download/install.sh | sudo bash

The installer will ask whether to install the agent, the server, or both, then configure and start the appropriate systemd services. For an agent, it asks how to reach the server: mDNS auto-discovery, a fixed URL, or a cloud account (see Cloud mode).

Update

curl -fsSL https://github.com/adambie/screenguard/releases/latest/download/install.sh | sudo bash -s -- --update

This downloads the latest binaries, replaces them, and restarts the services. Configs are not touched.

Uninstall

curl -fsSL https://github.com/adambie/screenguard/releases/latest/download/install.sh | sudo bash -s -- --uninstall

You will be asked separately whether to remove the config directory and the database.

Running the server in Docker

Two compose files are included. The agent always runs natively on the managed (child) machine — it interacts with the desktop session via DBus and cannot run inside a container.

SQLite (default) — docker-compose.yml

Uses host networking so mDNS auto-discovery works — agents on the LAN find the server without any manual configuration. The database is stored in a named Docker volume (screenguard-data) and survives restarts and image updates.

# 1. Clone the repo (or download docker-compose.yml + deploy/ separately)
git clone https://github.com/adambie/screenguard.git
cd screenguard

# 2. Edit docker-compose.yml and replace "change-me-in-production" with strong secrets

# 3. Start
docker compose up -d

# 4. Open the admin UI
#    http://<server-ip>:5000

To pin a specific release instead of always pulling latest, set the VERSION build arg in docker-compose.yml:

args:
  VERSION: v0.10.0

Postgres — docker-compose.postgres.yml

Spins up a Postgres container alongside the server. Useful if you want a proper relational backend or plan to move the database off the server host later. Uses bridge networking — mDNS auto-discovery does not work, so agents must be pointed at the server manually:

# /etc/screenguard/agent.toml on each managed machine
server_url = "ws://<server-host-ip>:8080"
# Edit docker-compose.postgres.yml and replace "change-me-in-production" with strong secrets
docker compose -f docker-compose.postgres.yml up -d

The DATABASE_URL environment variable controls which database the server connects to. When set to a postgres:// URL the server uses Postgres; when set to a sqlite: path (or left unset) it falls back to SQLite.

Bridge networking (SQLite)

If host networking is not an option for the default compose (rootless Podman, non-Linux Docker host, strict isolation), docker-compose.yml contains a commented-out bridge-mode configuration. The same mDNS caveat applies — you must configure the server address manually on every managed machine.

Updating

docker compose pull
docker compose up -d

Firewall

If the server and agents run on different machines, the agents need to reach the server's port (default 8080) over TCP.

ufw (Ubuntu/Debian)

sudo ufw allow 8080/tcp

firewalld (Fedora/RHEL)

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

mDNS auto-discovery uses UDP multicast on port 5353. This works out of the box on most home/office networks. If agents fail to find the server automatically, use a fixed URL instead (the installer offers this as an option), or ensure mDNS/multicast traffic is allowed between the machines.

First-run setup

  1. Server: after install, open the web UI. On first visit you will be prompted to create an admin account.

  2. Create a profile: in the web UI, go to Profiles → New profile. Set schedules and daily limits.

  3. Pair an agent: go to Agents. Each unpaired agent shows a pairing code in its logs:

    journalctl -u screenguard-agent -f
    

    Accept the code in the web UI to pair the agent with the server.

  4. Assign users: on the agent detail page, assign each local user account to a profile.

Locking modes

Each profile has two locking modes. The setting defaults to off, preserving ScreenGuard's existing behavior.

  • Default (preserve tasks off): when access is blocked, ScreenGuard locks the login session and terminates the graphical session after the configured grace period.
  • Preserve tasks on: when access is blocked, ScreenGuard locks and continues re-locking the login session without terminating it. Applications and unsaved work remain running. Once access is restored, the user returns to the existing session.

In both modes, Lock now zeroes the user's remaining allowance. The preserve-tasks setting changes only what happens to the graphical session while access remains blocked.

Configuration

Server — /etc/screenguard/server.toml

listen_addr      = "0.0.0.0"
listen_port      = 8080
db_path          = "/var/lib/screenguard/server.db"  # SQLite path (ignored when database_url is set)
# database_url   = "postgres://user:pass@host:5432/screenguard"  # use Postgres instead of SQLite
enable_mdns      = true
jwt_expiry_hours = 24

Agent — /etc/screenguard/agent.toml

# Leave commented out to use mDNS auto-discovery:
# server_url = "http://192.168.1.100:8080"

heartbeat_interval  = 10    # seconds
user_scan_interval  = 300   # seconds
cache_ttl_hours     = 48
min_uid             = 1000  # ignore system accounts below this UID

Cloud mode (experimental)

⚠️ Experimental and unsupported. The hosted cloud service, its endpoint, and this enrolment flow may change or be removed. Self-hosted / LAN use is unaffected — skip this section entirely and the agent behaves exactly as documented above.

Instead of running your own server on the LAN, an agent can pair with the hosted service at api.screenguard.cc and report to a cloud account.

At install time. Pass the account to the installer and it writes the config for you:

curl -fsSL https://github.com/adambie/screenguard/releases/latest/download/install.sh \
  | sudo bash -s -- --cloud-account=you@example.com

The interactive installer offers the same thing: choose Agent only, then pick Cloud account when it asks how to reach the server. Either way it creates /etc/screenguard/agent.toml with cloud_account set. (If agent.toml already exists it is not modified — add the line by hand, see below.)

On an already-installed agent. Add the account to the config and restart:

# /etc/screenguard/agent.toml
cloud_account = "you@example.com"
# cloud_url  = "wss://api.screenguard.cc/ws"   # override the endpoint if needed
sudo systemctl restart screenguard-agent

You can also pass it once on the command line (screenguard-agent --cloud-account you@example.com), but the systemd unit carries no flags, so without the config line the agent falls back to its saved binding on the next restart — persisting it in agent.toml is the supported way.

  • The account is an email that identifies your cloud account, not a server address. Use --server-url wss://host/ws (or cloud_url) to point at a different / self-run cloud endpoint.
  • With a cloud account set, mDNS discovery and server_url are ignored.
  • The machine then shows up as pending under that account and has to be approved there before it does anything.
  • Starting with a different cloud account re-pairs from scratch. If the agent was already paired (to a LAN server or another cloud account) and you start it with a different --cloud-account, it clears the old pairing and all downloaded rules and usage counters, then pairs anew. The machine is unmanaged until the new account approves it, and today's screen-time counter is reset to zero. Restarting with the same account, or with no account, does not wipe anything. To leave cloud mode, or to move to a new server IP, run screenguard-agent --reset (see "Agent reset" below).
  • An unknown / mistyped account gives no error — the agent just stays pending forever. This is intentional (it stops the service being used to test which emails have accounts).

Environment variable overrides:

Variable Description
SCREENGUARD_SERVER_CONFIG Path to server config file
SCREENGUARD_AGENT_CONFIG Path to agent config file
SCREENGUARD_SERVER_DB SQLite database path (default: /var/lib/screenguard/server.db)
DATABASE_URL Full database URL — overrides db_path. Use postgres://… for Postgres or sqlite:/path for SQLite
SCREENGUARD_SERVER_JWT_SECRET JWT signing secret

Web UI

The web UI is a small Flask app included in the webui/ directory. It is not installed as a service — it is intended for local administration while the server binary handles all agent communication.

cd webui
SERVER_URL=http://localhost:8080 uv run --with flask --with requests python app.py

Logs

journalctl -u screenguard-server -f
journalctl -u screenguard-agent -f

Agent reset (re-pair)

If an agent needs to be re-paired (e.g. after moving to a different server):

screenguard-agent --reset
sudo systemctl restart screenguard-agent

License

GPL-3.0 — see LICENSE.

About

Self-hosted parental controls for Linux. Define screen time schedules and daily limits per user profile. A systemd agent enforces rules on managed machines and locks sessions when time is up. Central server with REST API and web UI. Works offline. No cloud required.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages