Scripting & CI

Using mkunit in automation, CI/CD pipelines, and dotfiles.

Non-Interactive Mode

For scripts and CI, use --no-interactive to prevent prompts:

mkunit service myapp \
  --exec "./server" \
  --restart on-failure \
  --install \
  --no-interactive

mkunit also auto-detects non-interactive environments (no TTY, CI variable set).

Dry Run for Validation

Use --dry-run to generate unit files without writing them:

# Preview the unit file
mkunit service myapp --exec "./server" --dry-run

# Write to a specific location for review
mkunit service myapp --exec "./server" --dry-run > myapp.service

# Validate the generated file
mkunit validate ./myapp.service --strict

Exit Codes

mkunit uses standard exit codes for scripting:

Code Meaning
0Success
1General error
2Invalid arguments or missing required options
3Unit not found
4Validation failed
5Permission denied

Example: Deployment Script

#!/bin/bash
set -e

APP_NAME="myapp"
APP_DIR="/opt/myapp"
APP_USER="myapp"

# Create or update the service
sudo mkunit service "$APP_NAME" \
  --exec "$APP_DIR/bin/server" \
  --workdir "$APP_DIR" \
  --user "$APP_USER" \
  --env-file "$APP_DIR/.env" \
  --restart on-failure \
  --hardening \
  --system \
  --install \
  --no-interactive

# Reload and restart
sudo systemctl daemon-reload
sudo systemctl restart "$APP_NAME"

# Verify it's running
if mkunit status "$APP_NAME" --system -q; then
  echo "✓ $APP_NAME is running"
else
  echo "✗ $APP_NAME failed to start"
  mkunit logs "$APP_NAME" --system -n 50
  exit 1
fi

Example: GitHub Actions

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install mkunit
        run: cargo install mkunit

      - name: Generate unit file
        run: |
          mkunit service myapp \
            --exec "./server" \
            --restart on-failure \
            --hardening \
            --dry-run > myapp.service

      - name: Validate unit file
        run: mkunit validate ./myapp.service --strict

      - name: Deploy to server
        run: |
          scp myapp.service server:/tmp/
          ssh server 'sudo mv /tmp/myapp.service /etc/systemd/system/ && \
                      sudo systemctl daemon-reload && \
                      sudo systemctl restart myapp'

Example: Ansible Integration

- name: Create systemd service with mkunit
  command: >
    mkunit service {{ app_name }}
    --exec "{{ app_exec }}"
    --workdir "{{ app_dir }}"
    --user "{{ app_user }}"
    --restart on-failure
    --hardening
    --system
    --install
    --no-interactive
  become: yes
  notify: restart app

- name: Ensure service is enabled
  systemd:
    name: "{{ app_name }}"
    enabled: yes
    state: started
  become: yes

Example: Dotfiles Bootstrap

Set up user services when bootstrapping a new machine:

#!/bin/bash
# ~/.dotfiles/setup-services.sh

# SSH agent service
mkunit service ssh-agent \
  --exec "/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK" \
  --env "SSH_AUTH_SOCK=$HOME/.ssh/agent.sock" \
  --install --enable \
  --no-interactive

# Syncthing
mkunit service syncthing \
  --exec "/usr/bin/syncthing serve --no-browser --no-restart" \
  --restart on-failure \
  --install --enable \
  --no-interactive

# Start services
systemctl --user start ssh-agent syncthing

echo "Services installed and started"

JSON Output

Use --json for machine-readable output:

# List units as JSON
mkunit list --json | jq '.[] | select(.state == "active")'

# Get status as JSON
mkunit status myapp --json | jq '.pid'

# Validate with JSON output
mkunit validate myapp --json | jq '.warnings'

Idempotent Operations

mkunit commands are idempotent - running them multiple times has the same effect:

# Safe to run multiple times - just overwrites the unit file
mkunit service myapp --exec "./server" --install --no-interactive

# Remove is also safe if unit doesn't exist
mkunit remove myapp --force 2>/dev/null || true

Error Handling

#!/bin/bash
set -e

# Create service with error handling
if ! mkunit service myapp --exec "./server" --install --no-interactive 2>&1; then
  echo "Failed to create service"
  exit 1
fi

# Check if service is healthy after starting
systemctl --user start myapp
sleep 2

if ! mkunit status myapp -q; then
  echo "Service failed to start. Logs:"
  mkunit logs myapp -n 20
  exit 1
fi

See Also