luctl

command module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 3 Imported by: 0

README

luctl

A command-line tool for managing a self-hosted Luanti server. It wraps the ContentDB REST API and introduces a declarative luanti.toml project manifest — similar to package.json or pyproject.toml — so your server configuration, mod list, and world settings are all version-controlled in one place.


Why

The Luanti client ships a built-in Content browser, but it installs mods into the client's local directory — not a remote server. When running a self-hosted server, managing packages means dropping files into the server's mods/ directory by hand, then remembering to enable them in minetest.conf and world.mt.

luctl automates the entire workflow:

  • Package management — search ContentDB, install, update, and list mods and games. Installing a mod in a project directory automatically enables it in world.mt and records it in luanti.toml.

  • Project manifest (luanti.toml) — declares your server identity, mod list, filesystem paths, and arbitrary minetest.conf key/value pairs. Commit it to version control so the server is fully reproducible from a single file.

  • Config sync — push [config] values from luanti.toml into minetest.conf with one command, updating existing keys in-place without touching your comments.

  • Mod enable/disable — toggle mods in world.mt by name, without opening any file.


Requirements

  • Go 1.22+ (the project is built with 1.26)
  • Network access to https://content.luanti.org
  • A Luanti server with its data directory accessible on the local filesystem

Install

Build from source
git clone https://github.com/brylie/luctl
cd luctl/cli
go build -o luctl .

Move the binary somewhere on your $PATH:

mv luctl /usr/local/bin/
Cross-platform releases

Use GOOS and GOARCH to cross-compile a single static binary for any platform. No CGo, no runtime dependencies.

Platform Command
Linux (amd64) GOOS=linux GOARCH=amd64 go build -o luctl-linux-amd64 .
Linux (arm64) GOOS=linux GOARCH=arm64 go build -o luctl-linux-arm64 .
macOS (Apple Silicon) GOOS=darwin GOARCH=arm64 go build -o luctl-darwin-arm64 .
macOS (Intel) GOOS=darwin GOARCH=amd64 go build -o luctl-darwin-amd64 .
Windows (amd64) GOOS=windows GOARCH=amd64 go build -o luctl-windows-amd64.exe .

Usage

Run luctl --help for the full command reference, or luctl <command> --help for details on any subcommand. The two top-level namespaces are:

  • luctl package — search, install, update, list, enable, and disable packages
  • luctl project — init, install, status, sync, fmt a luanti.toml manifest
The luanti.toml project file

Run luctl project init in your server directory to create a manifest:

[server]
  name = "My Server"
  admins = ["alice"]

[paths]
  mods_dir  = "./data/mods"
  games_dir = "./data/games"
  world_dir = "./data/worlds/world"
  conf_file = "./data/main-config/minetest.conf"

[config]
  # Any minetest.conf key can go here
  enable_damage  = true
  creative_mode  = false
  server_announce = false

[packages]
  mods  = ["TenPlus1/farming", "sfan5/worldedit"]
  games = ["Luanti/minetest_game"]

Once the manifest exists:

  • luctl package install <author/name> installs to the correct directory, enables the mod in world.mt, and appends it to [packages] — all in one step.
  • luctl project install reproduces the full package list on a fresh machine.
  • luctl project sync pushes [config] values into minetest.conf.
  • luctl project fmt sorts and normalises the manifest in-place.

Server backups

luctl server backup create uploads a timestamped tar.gz of your world directory and minetest.conf to any S3-compatible provider (DigitalOcean Spaces, Backblaze B2, MinIO, AWS S3, …).

Backups are safe to run against a live server. SQLite world databases (map.sqlite, players.sqlite, etc.) are snapshotted with SQLite's VACUUM INTO command, which acquires only a shared read lock and produces a consistent point-in-time copy. SQLite auxiliary files (-journal, -wal, -shm) are excluded automatically.

Add a [backup] section to your luanti.toml:

[backup]
  bucket   = "my-luanti-backups"
  endpoint = "https://nyc3.digitaloceanspaces.com"   # region endpoint — NOT the bucket-specific URL
  region   = "nyc3"
  prefix   = "luanti/"

DigitalOcean Spaces: use the region endpoint (https://REGION.digitaloceanspaces.com), not the bucket-specific URL (https://BUCKET.REGION.digitaloceanspaces.com). The bucket-specific URL combined with path-style access (which luctl uses for provider compatibility) causes a doubled path and breaks listing.

Credentials

Never pass credentials on the command line — they appear in ps aux and shell history. Store them in a file readable only by the service account:

sudo install -d -m 700 /etc/luctl
sudo install -m 600 /dev/null /etc/luctl/credentials
sudo nano /etc/luctl/credentials   # fill in the two lines below, then save

/etc/luctl/credentials:

LUCTL_S3_ACCESS_KEY=your-access-key
LUCTL_S3_SECRET_KEY=your-secret-key

See .env.example for additional safe options (interactive read -s, direnv).

Scheduled backups — systemd timer (Ubuntu / Debian / Arch / Fedora)

Create the service unit /etc/systemd/system/luctl-backup.service:

[Unit]
Description=Luanti server backup
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=luanti
WorkingDirectory=/opt/luanti          # directory containing luanti.toml
ExecStart=/usr/local/bin/luctl server backup create
EnvironmentFile=/etc/luctl/credentials

# Basic hardening
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/opt/luanti

Replace User=luanti and WorkingDirectory= with the account and directory that own your server files. EnvironmentFile= loads credentials without exposing them in the process list or the journal.

Create the timer unit /etc/systemd/system/luctl-backup.timer:

[Unit]
Description=Daily Luanti server backup

[Timer]
OnCalendar=daily
RandomizedDelaySec=30min
Persistent=true

[Install]
WantedBy=timers.target

Enable and start:

systemctl daemon-reload
systemctl enable --now luctl-backup.timer

# Verify the timer is scheduled
systemctl list-timers luctl-backup.timer

# Run a backup immediately to test
systemctl start luctl-backup.service

# Check the result
journalctl -u luctl-backup.service -n 20
Scheduled backups — cron (alternative)

If you prefer cron, wrap the command in a script so the credentials are never on the cron command line (which is visible in ps aux during execution).

Create /usr/local/bin/luctl-backup (owned by root, executable only by the service user):

#!/bin/sh
# Load credentials from a protected file; they will not appear in the process list.
. /etc/luctl/credentials
exec /usr/local/bin/luctl server backup create
sudo chmod 700 /usr/local/bin/luctl-backup
sudo chown root:root /usr/local/bin/luctl-backup

Add to the crontab of the user that owns the server directory (crontab -e):

# Daily backup at 02:00, log to file
0 2 * * * /usr/local/bin/luctl-backup >> /var/log/luctl-backup.log 2>&1

Shell autocompletion

Cobra generates autocompletion scripts for bash, zsh, fish, and PowerShell:

# zsh
luctl completion zsh > "${fpath[1]}/_luctl"

# bash
luctl completion bash > /etc/bash_completion.d/luctl

# fish
luctl completion fish > ~/.config/fish/completions/luctl.fish

Development

Setup

Install mise, then run:

mise install                              # installs Go, golangci-lint, markdownlint-cli2, prek, …
prek install                              # register the pre-commit hook
prek install --hook-type pre-push         # register the pre-push hook (coverage gate)

That's it — all tools are pinned in mise.toml and installed locally to the project.

Day-to-day commands
# Run without building
go run . package search mobs

# Run tests
go test ./...

# Lint (requires golangci-lint via mise)
mise exec -- golangci-lint run ./...

# Lint markdown
mise exec -- markdownlint-cli2 "**/*.md"

The linter config is in .golangci.yml. It enforces correctness rules (errcheck, staticcheck, gosec), HTTP safety (noctx, bodyclose), and style (revive, nlreturn, godot). Markdown rules are in .markdownlint.json.

Pre-commit hooks

Install the hooks once after cloning:

mise install                              # install all tools (go, golangci-lint, markdownlint-cli2, prek, …)
prek install                              # register the pre-commit hook
prek install --hook-type pre-push         # register the pre-push hook (coverage gate)

Pre-commit hooks run on every git commit:

  • golangci-lint — full lint suite on all Go packages (./...)
  • markdownlint-cli2 — markdown style checks on staged .md files

The pre-push hook runs on every git push:

  • coverage — runs the full test suite and fails if total coverage drops below 80% (go run ./scripts/check_coverage.go; uses only Go, works on all platforms)

Run all hooks manually at any time:

mise exec -- prek run --all-files

Documentation

Overview

Command luctl is a CLI for managing a Luanti co-op server and its packages.

Directories

Path Synopsis
Package cmd implements the luctl CLI commands.
Package cmd implements the luctl CLI commands.
internal
backup
Package backup implements S3-compatible backup and restore for Luanti server data.
Package backup implements S3-compatible backup and restore for Luanti server data.
contentdb
Package contentdb provides a minimal client for the ContentDB REST API.
Package contentdb provides a minimal client for the ContentDB REST API.
project
Package project defines the luanti.toml project manifest format.
Package project defines the luanti.toml project manifest format.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL