This small RISC-V homeserver can host almost anything 🚀

Getting NixOS running with various web services and features on this StarFive VisionFive 2 SBC took some years but finally it’s taking shape.

If you’re reading this blog post, you reached our new tiny homeserver in the basement of our living community in Karlsruhe, Germany. It’s running behind a standard cable internet NAT and a 5V USB power supply. Attached is a 20TB external HDD, Bcachefs encrypted, storing backups, music and movies.

Linux mainline

First of all, this RISC-V SBC has great Linux mainline support. So the first thing to do was to test and boot the mainline kernel and further package it for NixOS. By flashing an up-to-date U-Boot bootloader, the SBC could also run NixOS from NVME SSD.

Using some patches by @magicquark, I was able to switch to Btrfs as a root filesystem, which will get useful for our backup approach explained later in the post. See here how you can generate a Btrfs-enabled rootfs image.

Small hardware, many features

Lets come to the interesting part, because this small board is able to host a lot of websites, web services and scripts. I’m using the NixOS config as a guidance to walk you through.

AirPrint server

Attached is an older but good working laser printer via USB. Using this snippet, it’s shared in the local network so you can print directly from any PC or smartphone using AirPrint.

hardware.printers = {
  ensurePrinters = [
    {
      name = "Brother_HL-2150N";
      location = "Home";
      deviceUri = "usb://Brother/HL-2150N%20series?serial=L8J996110";
      model = "drv:///brlaser.drv/br2150.ppd";
      ppdOptions.PageSize = "A4";
    }
  ];
  ensureDefaultPrinter = "Brother_HL-2150N";
};

services = {
  printing = {
    enable = true;
    openFirewall = true;
    drivers = [ pkgs.brlaser ];
    browsing = true;
    defaultShared = true;
    listenAddresses = [ "*:631" ];
    allowFrom = [ "all" ];
    extraConf = ''
      DefaultPaperSize A4
      ReadyPaperSizes A4
      DefaultLanguage de
    '';
  };

  avahi = {
    enable = true;
    nssmdns4 = true;
    openFirewall = true;
    publish = {
      enable = true;
      userServices = true;
    };
  };
};

Pixiecore PXE netboot server

This is a neat little service which will enable other devices in your network to boot Linux systems via PXE. It will serve the netboot.xyz multiboot image.

pixiecore = {
  enable = true;
  openFirewall = true;
  dhcpNoBind = true;
  kernel = "https://boot.netboot.xyz";
  port = 8081;
  ##};

Navidrome music streaming

I switched from Jellyfin to Navidrome music streaming service. It runs fine on RISC-V, is less memory intensive and super fast.

navidrome = {
  enable = true;
  settings = {
    EnableSharing = true;
    PlaylistsPath = "/mnt/audio/playlists";
    MusicFolder = "/mnt/audio/music";
  };
};

Prometheus and Grafana

Using these two services, we are going to gather some power consumption and solar power statistics soon. Well, new projects incoming …

grafana = {
  enable = true;
  settings = {
    server = {
      http_addr = "127.0.0.1";
      http_port = 8083;
      enforce_domain = true;
      enable_gzip = true;
      domain = "grafana.project-insanity.org";
    };
    analytics.reporting_enabled = false;
  };
};

prometheus = {
  enable = true;
  port = 9006;
  extraFlags = [
    "--storage.tsdb.retention.time=5y"
    "--storage.tsdb.retention.size=99GB"
  ];
};

Incremental full disk remote backups

Using Btrbk we snapshot the whole system filesystem and send it incrementally to a remote location at night.

btrbk.instances.picloudka = {
  # Every night at 3 am
  onCalendar = "*-*-* 3:00:00";
  settings = {
    ssh_identity = config.age.secrets.users-onny-ssh-privkey.path;
    ssh_user = "picloud";
    stream_compress = "lz4";
    # Keep daily snapshots for the past 7 days, weekly snapshots for 4 weeks,
    # and monthly snapshots for 12 months
    snapshot_preserve = "7d 4w 12m";
    snapshot_preserve_min = "7d";
    target_preserve = "7d 4w 12m";
    volume."/btr_pool" = {
      target = "ssh://10.250.0.4/mnt/picloud";
      subvolume = "picloud";
    };
  };
};

Nextcloud: Contacts, calendar and files

Of course, the web app Nextcloud shouldn’t be missing here.

nextcloud = {
  enable = true;
  hostName = "nextcloud.project-insanity.org";
  config = {
    adminpassFile = config.age.secrets.nextcloud-admin-pw.path;
    dbtype = "mysql";
  };
  database.createLocally = true;
  https = true;
  settings = {
    default_phone_region = "DE";
    trusted_proxies = [
      "10.250.0.1"
      "fdc9:281f:4d7:9ee9::1"
    ];
  };
  secretFile = config.age.secrets.nextcloud-secrets.path;
  extraApps = with config.services.nextcloud.package.packages.apps; {
    inherit mail deck calendar notes bookmarks polls contacts tasks;
    inherit twofactor_webauthn onlyoffice;
  };
  extraAppsEnable = true;
  maxUploadSize = "10G";
};

Git repository hosting

I also migrated from GitLab to Forgejo and it was a good choice in doing so. It’s also faster and simpler for smaller setups like here.

forgejo = {
  enable = true;
  database.type = "mysql";
  settings = {
    service.DISABLE_REGISTRATION = true;
    server = {
      DOMAIN = "git.project-insanity.org";
      ROOT_URL = "https://git.project-insanity.org";
    };
  };
};

All-in-one automated mail server

Self-hosting mail servers is still complicated and time-consuming. Using Stalwart mail server on NixOS I could automate some parts of it. See the wiki entry for more info. For now it’s working reliable and doesn’t require a lot of maintenance.

stalwart-mail = {
  enable = true;
  openFirewall = true;
  credentials = {
    stalwart-mail-cloudflare-secret = config.age.secrets.stalwart-mail-cloudflare-secret.path;
    stalwart-mail-user-onny = config.age.secrets.stalwart-mail-user-onny.path;
  };
  settings = {
    server = {
      hostname = "mx1.project-insanity.org";
      tls = {
        enable = true;
        implicit = true;
     };
     listener = {
        smtp = {
          protocol = "smtp";
          bind = "[::]:25";
          proxy.trusted-networks = [
           "10.250.0.1/32"
            "fdc9:281f:4d7:9ee9::1/128"
            "127.0.0.1/32"
            "::1/128"
          ];
        };
        submissions = {
          bind = "[::]:465";
          protocol = "smtp";
          tls.implicit = true;
        };
        imaps = {
          bind = "[::]:993";
          protocol = "imap";
          tls.implicit = true;
        };
        jmap = {
          bind = "[::]:8080";
          url = "https://mail.project-insanity.org";
          protocol = "http";
        };
        management = {
          bind = [ "127.0.0.1:8080" ];
          protocol = "http";
        };
      };
    };
    lookup = {
      default = {
        hostname = "mx1.project-insanity.org";
        domain = "project-insanity.org";
      };
    };
    acme."cloudflare" = {
      email = "onny@project-insanity.org";
      domains = [ "mx1.project-insanity.org" ];
      provider = "cloudflare";
      secret = "%{file:/run/credentials/stalwart-mail.service/stalwart-mail-cloudflare-secret}%";
    };
    session.auth = {
      mechanisms = "[plain]";
      directory = "'in-memory'";
      # FIXME allow sending from subaddresses
      must-match-sender = false;
    };
    storage.directory = "in-memory";
    session.rcpt.directory = "'in-memory'";
    directory."imap".lookup.domains = [ "project-insanity.org" ];
    directory."in-memory" = {
      type = "memory";
      principals = [
        {
          class = "individual";
          name = "onny@project-insanity.org";
          secret = "%{file:/run/credentials/stalwart-mail.service/stalwart-mail-user-onny}%";
         email = [ "onny@project-insanity.org" ];
        }
      ];
    };
    authentication.fallback-admin = {
     user = "admin";
      secret = "%{file:/run/credentials/stalwart-mail.service/stalwart-mail-user-onny}%";
    };
  };
};

Online knowledge base and wiki

Still the most reliable self-hosted wiki solution: Dokuwiki.

dokuwiki = {
  sites."wiki.project-insanity.org" = {
    settings = {
      title = "Project-Insanity";
      userewrite = 1;
      baseurl = "https://wiki.project-insanity.org";
      useacl = false;
    };
  };
};

Invoicing web app Invoiceplane

Invoiceplane is an open-source invoicing web app I’m using for my IT freelancer work.

invoiceplane = {
  sites = {
    "invoice.project-insanity.org" = {
      enable = true;
      settings = {
        SETUP_COMPLETED = true;
        DISABLE_SETUP = true;
        IP_URL = "https://invoice.project-insanity.org";
      };
      invoiceTemplates = [ invoiceplane-template-vtdirektmarketing ];
    };
  };
};

WordPress webiste hosting

We host several websites using WordPress including this blog post you’re currently reading.

wordpress = {
  sites = {
    "project-insanity.org" = {
      database = {
        createLocally = true;
        name = "wordpress_projectinsanity";
      };
      plugins = {
        inherit (pkgs.wordpressPackages.plugins)
          add-widget-after-content
          antispam-bee
          code-syntax-block
          wp-gdpr-compliance
          co-authors-plus
          wp-statistics
          wp-user-avatars
         opengraph
         simple-login-captcha
         simple-mastodon-verification
         disable-xml-rpc
         async-javascript
         webp-converter-for-media
         breeze
         jetpack
         jetpack-lite;
      };
      themes = {
        inherit (pkgs.wordpressPackages.themes)
          proton;
      };
      settings = {
        WP_DEFAULT_THEME = "proton";
        WP_HOME = "https://project-insanity.org";
        WP_SITEURL = "https://project-insanity.org";
        FORCE_SSL_ADMIN = true;
      };
    };
  };
};

Summary

For all this to work, I had to fix a lot of packages to cross-compile to RISC-V and wrote many pages of documentation on the NixOS wiki. This should help future adventurers who would like to test RISC-V architecture on NixOS and maybe want to run an own homeserver in the basement :)

But there’s still some things left. I haven’t found yet any video streaming service or small Mastodon server for the RISC-V SBC.

💬 Are you interested in our work or have some questions? Join us in our public Signal chat pi crew 👋
🪙 If you like our work or want to supprot us, you can donate MobileCoins to our address.

Leave a Reply

Your email address will not be published. Required fields are marked *