Skip to content

Latest commit

 

History

History
1103 lines (815 loc) · 22 KB

File metadata and controls

1103 lines (815 loc) · 22 KB

Appendix: Going Further

Overview

This appendix contains advanced topics, additional patterns, and ideas for extending your "Workstation as Code" setup. These topics go beyond the workshop basics and are provided for self-study and exploration. Not all have been fully tested at the time of writing - please submit PR's if you find any issues.

Table of Contents


Advanced BlueBuild

Multiple Images

You can maintain multiple image variants from one repository:

# recipes/workstation.yml - for your laptop
name: bluefin-workstation
base-image: ghcr.io/ublue-os/bluefin-main
modules:
  - type: rpm-ostree
    install:
      - wireshark
      - vlc
      - gimp

# recipes/server.yml - for servers
name: bluefin-server
base-image: ghcr.io/ublue-os/bluefin-main
modules:
  - type: rpm-ostree
    install:
      - podman
      - nginx
    remove:
      - firefox  # Don't need GUI apps

Update .github/workflows/build.yml to build both:

strategy:
  matrix:
    recipe:
      - workstation.yml
      - server.yml
steps:
  - uses: blue-build/github-action@v1
    with:
      recipe: recipes/${{ matrix.recipe }}

Custom Scripts

Run custom scripts during image build:

# config/scripts/post-install.sh
#!/bin/bash

# Set default editor system-wide
echo "export EDITOR=vim" > /etc/profile.d/editor.sh

# Configure automatic updates
systemctl enable rpm-ostreed-automatic.timer

# Set up custom system configuration
echo "Custom setup complete"

Reference in recipe.yml:

modules:
  - type: script
    scripts:
      - post-install.sh

Custom Repositories

Add third-party repositories:

modules:
  - type: rpm-ostree
    repos:
      - https://download.docker.com/linux/fedora/docker-ce.repo
    install:
      - docker-ce
      - docker-ce-cli
      - containerd.io

Flatpak Customization

Manage Flatpak applications:

modules:
  - type: default-flatpaks
    notify: true
    system:
      install:
        - org.mozilla.firefox
        - com.visualstudio.code
        - com.spotify.Client
      remove:
        # Remove apps you don't want from base image
        - org.gnome.Calculator

System Configuration Files

Layer in system-wide configurations:

modules:
  - type: files
    files:
      # Systemd service
      - source: services/my-service.service
        destination: /etc/systemd/system/my-service.service

      # Kernel parameters
      - source: sysctl.conf
        destination: /etc/sysctl.d/99-custom.conf

      # Firewall rules
      - source: firewall/custom.xml
        destination: /etc/firewalld/zones/custom.xml

Module Development

Create custom modules for reusability:

# modules/developer-tools.yml
name: developer-tools
type: rpm-ostree
install:
  - vim
  - git
  - tmux
  - htop
  - jq
  - ripgrep
  - bat
  - fd-find

# recipes/recipe.yml
modules:
  - from-file: developer-tools.yml

Advanced Gopass

Multiple Stores

Organize secrets into multiple stores:

# Work store
$ gopass init --store work --crypto age

# Personal store
$ gopass init --store personal --crypto age

# Use stores
$ gopass show work/gitlab/token
$ gopass show personal/banking/password

# List all stores
$ gopass mounts

Team Sharing

Share secrets with team members:

# Initialize shared store with multiple recipients
$ gopass init --store team --crypto age

# Add team member's public key
$ gopass recipients add --store team age1team-member-public-key

# Add secrets to shared store
$ gopass insert team/shared-api-key

# Team members can now decrypt with their private key

Gopass Git Workflows

Advanced Git integration:

# Configure automatic sync
$ gopass config autosync true

# Automatic push after changes
$ gopass config autopush true

# Automatic pull before access
$ gopass config autopull true

# Configure multiple remotes
$ cd ~/.local/share/gopass/stores/root
$ git remote add backup git@backup-server:gopass.git
$ git push backup main

Gopass with GPG (Alternative)

If you prefer GPG over AGE:

# Generate GPG key
$ gpg --full-generate-key

# Initialize gopass with GPG
$ gopass init your-gpg-key-id

# Works similarly but uses GPG keyring

Gopass Hooks

Automate actions with hooks:

# Pre-commit hook: run tests
$ cat > ~/.local/share/gopass/stores/root/.git/hooks/pre-commit << 'EOF'
#!/bin/bash
# Verify no plaintext secrets
if git diff --cached | grep -i "password.*=.*[^*]"; then
    echo "Potential plaintext password in commit!"
    exit 1
fi
EOF

$ chmod +x ~/.local/share/gopass/stores/root/.git/hooks/pre-commit

Gopass API

Use Gopass from other applications:

# JSON API
$ gopass show -o json work/api-key

# Use in scripts
#!/bin/bash
API_KEY=$(gopass show -o work/api-key)
$ curl -H "Authorization: Bearer $API_KEY" https://api.example.com

Password Generation Policies

Customize password generation:

# Configure default length
$ gopass config generate.autoclip false
$ gopass config generate.length 32

# Generate with specific requirements
$ gopass generate site.com/user 24 --symbols=false
$ gopass generate site.com/user 32 --strict  # Must include all char types

Advanced Chezmoi

Encrypted Files

Encrypt files in your dotfiles repository:

# Add encrypted file
$ chezmoi add --encrypt ~/.config/secret-config

# Chezmoi encrypts with AGE before committing
# You can safely commit encrypted files to public repos

Template Functions

Use advanced template functions:

# In a template file
{{ if eq .chezmoi.os "linux" }}
# Linux-specific config
{{ end }}

{{ if eq .chezmoi.osRelease.id "fedora" }}
# Fedora-specific
{{ end }}

{{ if stat (joinPath .chezmoi.homeDir ".work") }}
# If ~/.work file exists, this is a work machine
{{ end }}

# Include file contents
{{ include "common-aliases.sh" }}

# Execute command and include output
{{ output "command" "arg1" "arg2" }}

# Decrypt with AGE
{{ decrypt "encrypted-data" }}

# Template with JSON
{{ (index (fromJson (include "config.json")) "key") }}

Complex Template Example

# dot_zshrc.tmpl
# ZSH Configuration

# Machine-specific configuration
{{ if eq .chezmoi.hostname "work-laptop" }}
# Work machine
export WORK_ENV=true
export COMPANY_VPN_CONFIG=/etc/company/vpn.conf

# Work-specific aliases
alias vpn='sudo openvpn $COMPANY_VPN_CONFIG'
alias deploy='kubectl apply -f'

{{ else if eq .chezmoi.hostname "personal-desktop" }}
# Personal machine
export PERSONAL_ENV=true

# Gaming aliases
alias steam='flatpak run com.valvesoftware.Steam'

{{ end }}

# Common configuration across all machines
export EDITOR={{ .editor | default "vim" }}

# Pull secrets from Gopass
{{ if stat (joinPath .chezmoi.homeDir ".local/share/gopass") }}
export GITHUB_TOKEN={{ output "gopass" "show" "-o" "github/token" }}
{{ end }}

# Include common aliases
{{ include "aliases.sh" }}

Scripts: Run Once

Run setup scripts only once:

# run_once_install-fonts.sh.tmpl
#!/bin/bash
# This runs once, tracked by chezmoi

{{ if eq .chezmoi.os "linux" }}
echo "Installing custom fonts..."
mkdir -p ~/.local/share/fonts

# Download and install fonts
curl -L https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.0/FiraCode.zip -o /tmp/FiraCode.zip
unzip /tmp/FiraCode.zip -d ~/.local/share/fonts/
fc-cache -fv
{{ end }}

Scripts: Run on Every Apply

# run_onchange_update-vim-plugins.sh
#!/bin/bash
# Runs when this script changes

vim +PluginInstall +qall

Scripts: Before/After

# run_before_check-prerequisites.sh
#!/bin/bash
# Runs before chezmoi apply

if ! command -v git &> /dev/null; then
    echo "Git is required but not installed!"
    exit 1
fi

# run_after_restart-services.sh
#!/bin/bash
# Runs after chezmoi apply

systemctl --user daemon-reload
systemctl --user restart some-service

Machine-Specific Files

Only deploy certain files on certain machines:

# .chezmoiignore
{{ if ne .chezmoi.hostname "work-laptop" }}
.config/work/
{{ end }}

{{ if ne .chezmoi.os "darwin" }}
.config/macos/
{{ end }}

Chezmoi with Bitwarden

Alternative to Gopass - use Bitwarden:

# Install Bitwarden CLI
$ brew install bitwarden-cli  # or other method

# In template
{{ (bitwarden "item" "item-id").login.password }}

Chezmoi with 1Password

Use 1Password for secrets:

# In template
{{ (onepasswordRead "op://vault/item/field") }}

External Files

Include files from URLs:

# .chezmoiexternal.toml
[".vim/autoload/plug.vim"]
    type = "file"
    url = "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim"
    refreshPeriod = "168h"  # 1 week

Chezmoi Edit-Config

Store Chezmoi configuration in the repository:

# .chezmoi.toml.tmpl in repository
[data]
    email = "{{ .email }}"
    name = "{{ .name }}"

{{ if eq .chezmoi.hostname "work-laptop" }}
    editor = "code"
{{ else }}
    editor = "vim"
{{ end }}

Integration Patterns

SSH Keys in Chezmoi from Gopass

Complete example:

# private_dot_ssh/private_id_ed25519.tmpl
{{ output "gopass" "show" "-o" "ssh/personal_key" }}

# private_dot_ssh/id_ed25519.pub.tmpl
{{ output "gopass" "show" "-o" "ssh/personal_key_pub" }}

# private_dot_ssh/config.tmpl
Host github.com
    IdentityFile ~/.ssh/id_ed25519
    {{ if stat (joinPath .chezmoi.homeDir ".local/share/gopass") }}
    # Gopass available - enhanced config
    {{ end }}

Git Credentials from Gopass

# dot_gitconfig.tmpl
[user]
    name = {{ .name }}
    email = {{ .email }}

[credential]
    helper = store

[github]
    user = {{ .github_username }}

# dot_git-credentials.tmpl
{{ $github_token := output "gopass" "show" "-o" "github/token" -}}
https://{{ .github_username }}:{{ $github_token }}@github.com

AWS Credentials from Gopass

# private_dot_aws/credentials.tmpl
[default]
aws_access_key_id = {{ output "gopass" "show" "-o" "aws/access_key_id" }}
aws_secret_access_key = {{ output "gopass" "show" "-o" "aws/secret_access_key" }}

[work]
aws_access_key_id = {{ output "gopass" "show" "-o" "work/aws/access_key_id" }}
aws_secret_access_key = {{ output "gopass" "show" "-o" "work/aws/secret_access_key" }}

Environment Variables from Gopass

# dot_zshenv.tmpl
# Environment variables loaded before .zshrc

{{ if stat (joinPath .chezmoi.homeDir ".local/share/gopass") }}
# API Keys from Gopass
export OPENAI_API_KEY="{{ output "gopass" "show" "-o" "api/openai" }}"
export ANTHROPIC_API_KEY="{{ output "gopass" "show" "-o" "api/anthropic" }}"
{{ end }}

Systemd Services with Secrets

# private_dot_config/systemd/user/backup.service.tmpl
[Unit]
Description=Automated Backup Service

[Service]
Type=oneshot
Environment="BACKUP_KEY={{ output "gopass" "show" "-o" "backup/encryption_key" }}"
ExecStart=/usr/local/bin/backup.sh

[Install]
WantedBy=default.target

Real-World Examples

Full Developer Workstation

# recipe.yml
name: dev-workstation
base-image: ghcr.io/ublue-os/bluefin-dx-main
image-version: 40

modules:
  - type: rpm-ostree
    install:
      # Development tools
      - vim
      - tmux
      - git-delta
      - ripgrep
      - bat
      - fd-find
      - jq
      - httpie

      # Container tools
      - podman
      - buildah
      - skopeo

      # System tools
      - htop
      - ncdu
      - tree
      - wget

  - type: default-flatpaks
    system:
      install:
        - com.visualstudio.code
        - com.slack.Slack
        - us.zoom.Zoom
        - org.mozilla.firefox

Security-Focused Configuration

# recipe.yml
modules:
  - type: rpm-ostree
    install:
      - firejail  # Sandboxing
      - fail2ban  # Intrusion prevention
      - aide      # File integrity
      - lynis     # Security auditing
      - usbguard  # USB device control

  - type: files
    files:
      # Harden SSH
      - source: ssh/sshd_config
        destination: /etc/ssh/sshd_config.d/hardening.conf

      # Kernel hardening
      - source: sysctl/hardening.conf
        destination: /etc/sysctl.d/99-hardening.conf

Example config/files/sysctl/hardening.conf:

# Kernel hardening parameters
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.tcp_syncookies = 1

Media Production Workstation

# recipe.yml
modules:
  - type: rpm-ostree
    install:
      - ffmpeg
      - pipewire
      - wireplumber

  - type: default-flatpaks
    system:
      install:
        - org.kde.kdenlive       # Video editing
        - org.gimp.GIMP          # Image editing
        - org.audacityteam.Audacity  # Audio editing
        - org.blender.Blender    # 3D modeling
        - com.obsproject.Studio  # Streaming

Additional Tools

Just (Command Runner)

Simplify common tasks:

# Install just
$ sudo rpm-ostree install just

# Create justfile in your home directory
$ chezmoi add ~/.justfile

Example .justfile:

# Update entire system
update:
    rpm-ostree upgrade
    flatpak update -y
    chezmoi update
    gopass sync

# Backup everything
backup:
    #!/bin/bash
    cd ~/.local/share/gopass/stores/root && git push
    cd ~/.local/share/chezmoi && git push
    echo "Backup complete"

# Full system check
check:
    rpm-ostree status
    gopass ls
    chezmoi managed

Nix Package Manager

Use Nix for additional packages:

# Install Nix (in your custom image)
# Add to recipe.yml scripts

# Or use home-manager (Nix-based dotfiles manager)
# Alternative to Chezmoi if you prefer Nix

Ansible for Post-Install

Use Ansible for complex setup:

# run_once_ansible-setup.sh
#!/bin/bash

if ! command -v ansible &> /dev/null; then
    pip install --user ansible
fi

ansible-playbook ~/.config/ansible/setup.yml

Terraform for Infrastructure

Define your infrastructure as code alongside your workstation:

# infrastructure/main.tf
resource "github_repository" "dotfiles" {
  name        = "dotfiles"
  description = "My dotfiles"
  visibility  = "private"
}

resource "github_repository" "gopass_store" {
  name        = "gopass-store"
  description = "Encrypted password store"
  visibility  = "private"
}

Common Recipes

Recipe: Conditional Package Installation

Only install packages on certain machines:

# recipe.yml
modules:
  - type: script
    scripts:
      - conditional-packages.sh
# config/scripts/conditional-packages.sh
#!/bin/bash

HOSTNAME=$(hostname)

if [[ "$HOSTNAME" == "work-laptop" ]]; then
    rpm-ostree install \
        openvpn \
        networkmanager-openvpn
fi

Recipe: Theme and Appearance

modules:
  - type: rpm-ostree
    install:
      - gnome-tweaks
      - gnome-extensions-app

  - type: default-flatpaks
    system:
      install:
        - org.gtk.Gtk3theme.Adwaita-dark
# In Chezmoi: run_once_configure-theme.sh
#!/bin/bash

# Set dark theme
gsettings set org.gnome.desktop.interface gtk-theme 'Adwaita-dark'
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'

# Install GNOME extensions
# ...

Recipe: Development Containers

Set up toolbox containers automatically:

# run_once_create-toolboxes.sh
#!/bin/bash

# Create development toolboxes
toolbox create -d fedora:40 development
toolbox create -d ubuntu:22.04 ubuntu-dev

# Python development container
podman pull python:3.12

Recipe: Backup Automation

# private_dot_local/bin/executable_backup.sh.tmpl
#!/bin/bash
# Automated backup script

BACKUP_KEY="{{ output "gopass" "show" "-o" "backup/encryption_key" }}"
BACKUP_DEST="{{ output "gopass" "show" "-o" "backup/destination" }}"

# Backup important directories
restic backup \
    --password-file=<(echo "$BACKUP_KEY") \
    --repo="$BACKUP_DEST" \
    ~/.local/share/gopass \
    ~/.local/share/chezmoi \
    ~/Documents

# run_after_setup-backup-service.sh
systemctl --user enable --now backup.timer

Performance Optimization

Reduce Build Times

# Use layer caching effectively
modules:
  # Install large, infrequently changing packages first
  - type: rpm-ostree
    install:
      - kernel-devel
      - gcc
      - make

  # Install frequently changing packages later
  # This way changes don't invalidate early layers

Optimize Gopass

# Disable automatic sync for faster access
$ gopass config autosync false

# Sync manually when needed
$ gopass sync

Optimize Chezmoi

# Use --force to skip diff checking
$ chezmoi apply --force

# Only apply specific files
$ chezmoi apply ~/.zshrc ~/.gitconfig

# Use .chezmoiignore to skip large directories

Parallel Builds

# .github/workflows/build.yml
strategy:
  matrix:
    variant: [main, nvidia]
  max-parallel: 2  # Build multiple variants simultaneously

Security Hardening

AGE Key Protection

# Store AGE key on hardware token (YubiKey)
# Requires age-plugin-yubikey
$ age-plugin-yubikey --generate > key.txt

# Use with Gopass
$ gopass config age.identities /path/to/yubikey/identity

GPG Key on Hardware

# Use YubiKey for GPG (alternative to AGE)
# Follow YubiKey GPG setup guide
# Store GPG key on hardware token

SSH Key on Hardware

# Use hardware token for SSH (PIV)
# YubiKey, SoloKey, etc.

# Generate key on token
$ ykman piv keys generate --algorithm ECCP256 9a /tmp/public.pem

# Use with SSH
$ ssh-add -s /usr/lib/libykcs11.so

Encrypted Home Directory

# Use systemd-homed for encrypted home
# Set up during installation or migrate later

$ homectl create username \
    --disk-size=100G \
    --storage=luks

Image Signing Verification

# Verify signed images before deployment
# In recipe.yml
modules:
  - type: signing
    type: signing

# Verify manually
cosign verify ghcr.io/username/bluefin-custom:latest

Resources and References

Official Documentation

Community Resources

Example Repositories

Browse others' configurations for inspiration:

Learning Resources

  • Fedora Silverblue/Kinoite: Understanding atomic desktops
  • OCI Images: Container image concepts
  • Git: Version control fundamentals
  • YAML: Configuration syntax
  • Shell Scripting: Automation

Tools and Utilities

Security Resources

Blog Posts and Tutorials

Search for:

  • "Fedora Silverblue setup"
  • "Bluefin custom image"
  • "Dotfiles management"
  • "Infrastructure as Code workstation"
  • "Immutable desktop Linux"

Getting Help

Before Asking

  1. Check documentation for the specific tool
  2. Search GitHub issues in relevant repositories
  3. Review example configurations
  4. Test in a VM or toolbox first

Where to Ask

  • GitHub Discussions: Project-specific questions
  • Discord/Slack: Real-time community help
  • Forums: Fedora Discourse, Reddit
  • Stack Overflow: Technical problems

How to Ask

Good questions include:

  • What you're trying to do
  • What you've tried
  • Error messages (complete, not snippets)
  • Your configuration (relevant parts)
  • Your environment (Fedora version, etc.)

Contributing Back

When you solve a problem:

  • Document it in your repository
  • Share in community spaces
  • Consider contributing to docs
  • Help others with similar issues

Conclusion

The concepts you've learned in this workshop extend far beyond the specific tools:

  • Declarative Configuration: Define desired state, not steps
  • Version Control Everything: Git as the source of truth
  • Automation: Reduce manual steps to zero
  • Reproducibility: Identical results every time
  • Testability: Try changes safely
  • Documentation as Code: Configuration is self-documenting

These principles apply to:

  • Workstations (what we covered)
  • Servers
  • Cloud infrastructure
  • Network configuration
  • Application deployment
  • And more...

You now have a foundation to apply Infrastructure as Code principles to any computing environment.


Final Thoughts

Start small. Don't try to automate everything at once.

Begin with:

  1. Dotfiles (low risk, high value)
  2. Secrets (improve security)
  3. OS customization (when comfortable)

Iterate based on your needs. Add complexity only when it provides clear value.

The goal isn't perfection - it's improvement. Every configuration file you version control is progress. Every secret you encrypt properly is safer. Every manual step you automate is time saved.

Welcome to treating your workstation like production infrastructure.

Your laptop is cattle now. Enjoy the freedom.


Previous: Lab 6: Putting It All Together