homelab infrastructure services

Creating a New Machine#

This guide covers setting up a new machine in the tinsnip infrastructure, including prerequisites, NFS setup, machine configuration, and testing.

Decisions to Make First#

Before starting, you need to decide on these key inputs:

1. Service Name#

Give the thing you are making a name - this might be the product name (lldap or caddy) or a name which wraps a product of your own like gazette or daffodyl.

  • Currently configured: tinsnip, gazette
  • You can add more in the future

2. Environment#

Decide if this is for production use or testing. This affects which UID and ports get assigned.

  • prod - Production environment (UID: xxx00)
  • test - Test environment (UID: xxx10)

3. NAS Server#

Provide the hostname or IP address of your NAS where data will be stored.

  • Hostname example: DS412plus, nas.local
  • IP example: 192.168.1.100

4. Sheet (Optional)#

If you're running services for multiple organizations or want to separate your services from the default, set a custom sheet.

  • Default: dynamicalsystem
  • Custom: Your organization name (e.g., mycompany)

Prerequisites#

Machine Requirements#

  • OS: Ubuntu 20.04 or later (tested on 20.04 and 22.04)
  • User: Account with sudo privileges
  • Network: Access to NAS and internet for Docker installation
  • Resources: At least 2GB RAM, 10GB disk space

NAS Requirements#

  • Synology NAS with DSM 6.x or later
  • SSH access to NAS with administrative privileges
  • Available storage for service data

Verify Prerequisites#

# Check Ubuntu version
lsb_release -a

# Check sudo access
sudo -v && echo "✓ sudo access confirmed" || echo "✗ Need sudo access"

# Test NAS connectivity
ping -c 1 your-nas-hostname

Overview#

Creating a new machine involves:

  1. Sheet Configuration: Set the organizational sheet (defaults to dynamicalsystem)
  2. NAS Setup: Create NFS exports with proper UID mapping
  3. Machine Setup: Install and configure the machine environment
  4. Service Deployment: Deploy services with proper isolation
  5. Testing: Validate the complete setup

Step 1: Tinsnip Sheet Configuration#

The system supports configurable sheets for multi-tenant deployments. The sheet affects NFS paths and XDG directory organization.

Setting the Sheet#

Option 1: Environment Variable

export TIN_SHEET=mycompany
./machine/setup.sh tinsnip test DS412plus

Option 2: System-wide Configuration

echo "mycompany" | sudo tee /etc/tinsnip-sheet
./machine/setup.sh tinsnip test DS412plus

Default: If no sheet is configured, dynamicalsystem is used.

For detailed sheet configuration options, see SHEET_CONFIGURATION.md.

Step 2: NAS Setup (Critical)#

Create Directory Structure#

On your Synology NAS, create the directory structure for your sheet:

# For topsheet (default sheet, most users deploy here)
/volume1/topsheet/
├── station/
│   ├── prod/  (will be owned by UID 50000)
│   └── test/  (will be owned by UID 50010)
└── gazette/
    ├── prod/  (will be owned by UID 50100)
    └── test/  (will be owned by UID 50110)

# For custom sheet (infrastructure)
/volume1/tinsnip/infrastructure/
├── station/
│   ├── prod/  (will be owned by UID 10000)
│   └── test/  (will be owned by UID 10010)
└── gazette/
    ├── prod/  (will be owned by UID 11000)
    └── test/  (will be owned by UID 11110)

Create directories and set ownership:

# SSH into NAS
ssh admin@synology-ip

# Create directory structure (replace {sheet} with your sheet)
sudo mkdir -p /volume1/{sheet}/{tinsnip,gazette}/{prod,test}

# Set ownership to match service UIDs
sudo chown 11000:11000 /volume1/{sheet}/tinsnip/prod
sudo chown 11010:11010 /volume1/{sheet}/tinsnip/test
sudo chown 11100:11100 /volume1/{sheet}/gazette/prod
sudo chown 11110:11110 /volume1/{sheet}/gazette/test

# Example for custom sheet:
sudo mkdir -p /volume1/mycompany/{tinsnip,gazette}/{prod,test}
sudo chown 11000:11000 /volume1/mycompany/tinsnip/prod
sudo chown 11010:11010 /volume1/mycompany/tinsnip/test
sudo chown 11100:11100 /volume1/mycompany/gazette/prod
sudo chown 11110:11110 /volume1/mycompany/gazette/test

Important: The chown commands ensure the directories are owned by the correct UIDs before NFS export. This is critical for proper permissions.

Configure NFS Exports#

IMPORTANT: The Synology DSM GUI cannot map to specific UIDs (11000+), so you must configure exports via SSH.

Use the NFS export generator to create the exact rules you need:

# Interactive mode - prompts for all inputs
cd machine
./generate_nfs_exports.sh

# Direct mode - provide all parameters
./generate_nfs_exports.sh tinsnip test dynamicalsystem ubuntu-machine.local

# Example output shows:
# - Directory creation commands
# - Ownership commands  
# - Export rules to add to /etc/exports
# - Test commands

Option 2: Manual Configuration#

  1. SSH into Synology NAS:
ssh admin@synology-ip
  1. Edit /etc/exports file:
sudo vi /etc/exports
  1. Add export rules (CRITICAL: Use TAB characters between path and options):

Use the output from the generator above, or manually create rules like:

/volume1/{sheet}/{service}/{env}	{client-host}(rw,async,no_subtree_check,all_squash,anonuid={uid},anongid={uid})
  1. Reload NFS exports:
sudo exportfs -ra
  1. Verify exports are active:
sudo exportfs -v

Step 3: Machine Setup#

Install Machine Setup Tools#

The machine setup tools provide automated scripts for creating service users, mounting NFS, and installing rootless Docker.

# One-line installation - downloads scripts to ~/.local/opt/dynamicalsystem.machine
curl -fsSL "https://tangled.sh/dynamicalsystem.com/machine/raw/main/install.sh?$(date +%s)" | bash

# Navigate to installation directory
cd ~/.local/opt/dynamicalsystem.machine

What this installs:

  • setup.sh - Main orchestration script for service environment creation
  • scripts/ - Helper scripts for NFS mounting, Docker installation, etc.
  • machine/ - Service-specific setup scripts with sheet support

Run Validations (Optional)#

Before making any system changes, you can optionally validate the setup logic:

cd machine
./validate.sh

Running validations is recommended but not required.

Create Service Environment#

Run the setup script from the machine directory:

Interactive setup (recommended for first time):

cd machine
./setup.sh
# Will prompt for:
# - Service name (tinsnip or gazette)
# - Environment (test or prod)
# - NAS server hostname/IP

Direct setup (if you know the parameters):

cd machine
./setup.sh tinsnip test DS412plus    # Creates tinsnip-test environment
./setup.sh gazette prod 192.168.1.100  # Creates gazette-prod environment

# With custom sheet:
TIN_SHEET=mycompany ./setup.sh tinsnip prod nas.mycompany.local

What Gets Created#

The setup creates:

  1. Service User: tinsnip-test (UID 11010) with dedicated home directory
  2. NFS Mount: /mnt/tinsnip-testDS412plus:/volume1/{sheet}/tinsnip/test
  3. XDG Integration: Symlinks in ~/.local/{state,share,config}/{sheet}/@tinsnip
  4. Rootless Docker: Installed for service user with privileged port support
  5. Directory Structure:
    /mnt/tinsnip-test/
    ├── state/     # Service state (logs, history)
    ├── data/      # Service data files
    └── config/    # Service configuration
    

Step 4: Service Deployment#

Switch to Service User#

# Switch to service user context
sudo -u tinsnip-test -i

Deploy Service#

Create service configuration in the mounted NFS directory:

cd /mnt/tinsnip-test
mkdir -p service/lldap

# Create docker-compose.yml for the service
cat > service/lldap/docker-compose.yml << 'EOF'
services:
  lldap:
    image: lldap/lldap:stable
    ports:
      - "11010:3890"    # LDAP port (UID-based)
      - "11011:17170"   # Web UI port (UID-based)
    volumes:
      - ../data:/data
    user: "11010:11010"  # Run as service UID
    environment:
      - LLDAP_JWT_SECRET_FILE=/data/jwt_secret
      - LLDAP_KEY_SEED_FILE=/data/key_seed
      - LLDAP_BASE_DN=dc=home,dc=local
EOF

# Deploy the service
cd service/lldap
docker compose up -d

Step 5: Testing#

Basic Validation#

# Check service user
id tinsnip-test  # Should show UID 11010

# Verify NFS mount
mount | grep /mnt/tinsnip-test
ls -la /mnt/tinsnip-test/  # Should show correct ownership

# Check XDG symlinks
ls -la ~/.local/state/dynamicalsystem/@tinsnip  # Should point to /mnt/tinsnip-test/state

# Test Docker
sudo -u tinsnip-test docker --version
sudo -u tinsnip-test docker run hello-world

Service Testing#

# Check service status
sudo -u tinsnip-test -i
cd /mnt/tinsnip-test/service/lldap
docker compose ps

# Test service connectivity
curl -I http://localhost:11011  # Web UI
ldapsearch -H ldap://localhost:11010 -b "dc=home,dc=local"  # LDAP

VM Testing Plan#

For comprehensive testing, follow the VM Test Plan:

  1. Function Tests: Validate UID calculation and port allocation
  2. Partial Tests: Test user creation and NFS connectivity
  3. Full Setup: Complete service environment creation
  4. Service Deployment: Deploy and test actual services

Troubleshooting#

NFS Mount Issues#

# Test NFS connectivity manually (adjust path for your sheet)
sudo mount -t nfs DS412plus:/volume1/{sheet}/tinsnip/test /tmp/test-mount
ls -la /tmp/test-mount  # Should show UID 11010 ownership
sudo umount /tmp/test-mount

# Check NFS exports on server
ssh admin@DS412plus sudo exportfs -v

Permission Issues#

# Verify UID mapping
sudo -u tinsnip-test touch /mnt/tinsnip-test/test-file
ls -la /mnt/tinsnip-test/test-file  # Should show UID 11010

# Check from NAS side (adjust path for your sheet)
ssh admin@DS412plus ls -la /volume1/{sheet}/tinsnip/test/

Docker Issues#

# Check rootless Docker status
sudo -u tinsnip-test systemctl --user status docker

# Test privileged ports
sudo -u tinsnip-test docker run -p 80:80 nginx:alpine

Rollback Plan#

If setup fails or needs to be undone:

# Stop containers
sudo -u tinsnip-test -i bash -c 'docker stop $(docker ps -q)' || true

# Remove service user
sudo pkill -u tinsnip-test || true
sudo userdel -r tinsnip-test

# Unmount NFS
sudo umount /mnt/tinsnip-test || true
sudo rm -rf /mnt/tinsnip-test

# Clean up XDG symlinks (adjust paths for your sheet)
rm -rf ~/.local/state/{sheet}/@tinsnip
rm -rf ~/.local/share/{sheet}/@tinsnip  
rm -rf ~/.config/{sheet}/@tinsnip

Security Considerations#

  • Each service runs under dedicated UID for complete isolation
  • NFS all_squash ensures consistent file ownership
  • Rootless Docker provides container isolation without root privileges
  • Services cannot access each other's data due to UID separation

Next Steps#

After successful machine creation:

  1. Service Configuration: Configure service-specific settings
  2. Integration Testing: Test with other tinsnip services
  3. Monitoring Setup: Add service monitoring and logging
  4. Backup Validation: Ensure data backup processes work

For service-specific deployment patterns and UID conventions, see DEPLOYMENT_STRATEGY.md.