Compare commits

..

No commits in common. "aa53257d9b2ff5bffc5182eccaf010f6b63b836f" and "0af8ac86b93f5c039311f8d9aba72e6f0934f879" have entirely different histories.

11 changed files with 27 additions and 689 deletions

View file

@ -1,16 +0,0 @@
name: "build demo images"
on:
push:
branches:
- main
jobs:
build:
name: Build NixOS images
runs-on: nixos
steps:
- name: install node
run: nix-env -iA nixpkgs.nodejs_20
- name: checkout repository
uses: actions/checkout@v4
- name: run generator
run: nix-shell -p nixos-generators --run "nixos-generate -c inventory/demo-configuration.nix -f proxmox-lxc"

2
.gitignore vendored
View file

@ -1,6 +1,4 @@
.DS_Store
*.qcow2
result
node_modules
cache
dist

View file

@ -36,7 +36,7 @@ export default defineConfig({
{
text: 'Getting started',
items: [
{ text: 'Local installation', link: '/installation' },
{ text: 'Generate configuration', link: '/generate' },
{ text: 'Build virtual machine', link: '/build-virtual-machine' },
]
}

View file

@ -1,19 +0,0 @@
# Build images based on your generated configurations
Your Nix configurations are located in the `inventory` folder you can build and run the configuration file you want by running nixos-generate from the NixiN root folder. If the build succeeds, the last output is a command located in your nix store to run your vm.
## Images formats
Check <https://github.com/nix-community/nixos-generators?tab=readme-ov-file#supported-formats> to find available formats.
For example, for qemu-kvm runner
```bash
nixos-generate -c inventory/demo-configuration.nix -f vm
```
(nixos-generate is installed by nix-shell, check `shell.nix` to see the packages added)
## Cross compile
Check <https://github.com/nix-community/nixos-generators?tab=readme-ov-file#cross-compiling>.

View file

@ -1,77 +1,10 @@
<script>
export default {
beforeMount() {
this.availableServices.forEach((s, i) => {
s.inBundle = []
})
this.availableBundles.forEach((b, i) => {
b.services.forEach((s) => {
this.availableServices.find(item => item.id === s).inBundle.push(b.id);
})
})
},
data() {
return {
netconf: 'autoconfig',
networkingHostname: '',
networkingDomain: 'distrilab.eu',
availableBundles: [{
"id": "writeCollectively",
"name": "Write collectively : pads",
"services": [
'hedgedoc', 'nextcloud'
]
},
{
"id": "forge",
"name": "Forge : git repo, CI/CD workers, and NixiN",
"services": [
'forgejo', 'forgejoRunner', 'nixin'
]
},
{
"id": "socialMedia",
"name": "Social media: hosted social medias in activitypub web-apps",
"services": [
'gotosocial', 'peertube', 'lemmy'
]
}],
availableServices: [{
"id": "hedgedoc",
"name": "Hedgedoc : realtime collaborative markdown editor"
},
{
"id": "forgejo",
"name": "Forgejo : git hosting"
},
{
"id": "forgejoRunner",
"name": "Forgejo runner : CD/CI runner for Forgejo"
},
{
"id": "gotosocial",
"name": "Gotosocial : personal light activityPub social media"
},
{
"id": "peertube",
"name": "Peertube : video hosting platform with activityPub"
},
{
"id": "lemmy",
"name": "Lemmy : reddit alternative with activityPub"
},
{
"id": "nextcloud",
"name": "Nextcloud : personnal cloud"
},
{
"id": "nixin",
"name": "NixiN : web ui for configurations"
}],
nixinBundles: [],
nixinServices: [],
networkingDomain: 'distrilab.org',
timezone: 'Etc/UTC',
locale: 'en_US.UTF-8',
user: 'operator',
@ -85,13 +18,6 @@ export default {
} else {
this.networkingDomain = ''
}
},
selectServices(services) {
services.forEach((s) => {
if (this.nixinServices.indexOf(s) === -1) {
this.nixinServices.push(s)
}
})
}
}
}
@ -105,23 +31,27 @@ export default {
<strong>Choose your network configuration</strong>
<label>
<input type="radio" v-model="netconf" name="netconf" value="autoconfig"
@click="netconfHasBeenChanged('autoconfig')">I'm a noob in network config, I trust you to provide networking
for me (ipv6 only)</label>
@click="netconfHasBeenChanged('autoconfig')">I'm a
noob in
network config, I trust
you to provide networking for me (ipv6 only)</label>
<label>
<input type="radio" v-model="netconf" name="netconf" value="publicip" @click="netconfHasBeenChanged">My server
has a public ip that I can provide
has a public ip that I can
provide
</label>
<label>
<input type="radio" v-model="netconf" name="netconf" value="localnetwork" @click="netconfHasBeenChanged">My
router is set so that my local machine is accessible on the public network
router is set so that my local
machine is accessible on the public network
</label>
<label>
<input type="radio" v-model="netconf" name="netconf" value="wireguard" @click="netconfHasBeenChanged">My server
can use a wireguard server i can configure
can use a wireguard server i
can configure
</label>
</div>
</div>
<div class="form-row">
<div class="form-cell">
<label>Machine network name</label>
@ -138,33 +68,14 @@ export default {
</div>
<h2>Usage bundles</h2>
<div class="form-row">
<div class="form-cell">
<strong>Choose your usage bundles (multiple choices possible if your machine can handle it)</strong>
<div v-for="bundle in availableBundles">
<label>
<input type="checkbox" v-model="nixinBundles" :id="bundle.id" :value="bundle.id"
@click="selectServices(bundle.services)" />
{{ bundle.name }}
</label>
</div>
</div>
</div>
<h2>Services</h2>
<div v-if="nixinBundles.length === 0">👆 Choose any upper bundle to make associated services appear.</div>
<div v-for="service in availableServices">
<label v-if="service.inBundle.some(ai => nixinBundles.includes(ai))">
<input type="checkbox" v-model="nixinServices" :id="service.id" :value="service.id" />
{{ service.name }}
</label>
</div>
<h2>Applications packages</h2>
<h2>Other configuration</h2>
Operating UNIX user name<br />
Operating UNIX user password<br />
Timezone<br />
Locale<br />
<h2>Advanced configuration</h2>
Operating UNIX user name
Operating UNIX user password
Timezone
Locale
<h2>Auto-generated configuration.nix file</h2>
<pre>
@ -176,6 +87,9 @@ export default {
./hardware-configuration.nix
];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking = {
hostName = "{{ networkingHostname }}";
domain = "{{ networkingDomain }}";
@ -222,453 +136,6 @@ export default {
time.timeZone = "{{ timezone }}";
i18n.defaultLocale = "{{ locale }}";
<div v-if="nixinServices.includes('gotosocial')">
{
services.gotosocial = {
enable = true;
setupPostgresqlDB = true;
settings = {
application-name = "My GoToSocial";
host = "gotosocial.example.com";
protocol = "https";
bind-address = "127.0.0.1";
port = 8080;
};
};
}
{
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
clientMaxBodySize = "40M";
virtualHosts = with config.services.gotosocial.settings; {
"${host}" = {
enableACME = true;
forceSSL = true;
locations = {
"/" = {
recommendedProxySettings = true;
proxyWebsockets = true;
proxyPass = "http://${bind-address}:${toString port}";
};
};
};
};
};
}
</div>
<div v-if="nixinServices.includes('peertube')">
networking.extraHosts = ''
127.0.0.1 peertube.local
'';
environment.etc = {
"peertube/password-posgressql-db".text = "test123";
"peertube/password-redis-db".text = "test123";
};
services = {
peertube = {
enable = true;
localDomain = "peertube.local";
enableWebHttps = false;
database = {
host = "127.0.0.1";
name = "peertube_local";
user = "peertube_test";
passwordFile = "/etc/peertube/password-posgressql-db";
};
redis = {
host = "127.0.0.1";
port = 31638;
passwordFile = "/etc/peertube/password-redis-db";
};
settings = {
listen.hostname = "0.0.0.0";
instance.name = "PeerTube Test Server";
};
};
postgresql = {
enable = true;
enableTCPIP = true;
authentication = ''
hostnossl peertube_local peertube_test 127.0.0.1/32 md5
'';
initialScript = pkgs.writeText "postgresql_init.sql" ''
CREATE ROLE peertube_test LOGIN PASSWORD 'test123';
CREATE DATABASE peertube_local TEMPLATE template0 ENCODING UTF8;
GRANT ALL PRIVILEGES ON DATABASE peertube_local TO peertube_test;
ALTER DATABASE peertube_local OWNER TO peertube_test;
\connect peertube_local
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS unaccent;
'';
};
redis.servers.peertube = {
enable = true;
bind = "0.0.0.0";
requirePass = "test123";
port = 31638;
};
};
</div>
<div v-if="nixinServices.includes('lemmy')">
let
# add nginx reverse proxy and ACME web certificate
add_nginx = true;
nginx_ports = [ 80 443 ];
lemmy = {
upstreamName = "lemmy";
dataDir = "/var/lib/lemmy";
ip = "127.0.0.1";
port = 1234;
# TODO: Change this domain to your own
domain = "lemmy.example.com";
};
lemmy-ui = {
upstreamName = "lemmy-ui";
ip = "127.0.0.1";
port = 8536;
};
pict-rs = {
ip = "127.0.0.1";
port = 8080;
};
acmeDomain = lemmy.domain;
nginxVhost = lemmy.domain;
in {
security.acme = lib.mkIf add_nginx {
# TODO: change this to true if you accept
acceptTerms = false;
defaults = {
# TODO: you will receive a notification if automatic certificate renewal fails
email = "postmaster@${lemmy.domain}";
# TODO: put your dns provider here: https://go-acme.github.io/lego/dns/
dnsProvider = "";
# TODO: this file should contain environment variables expected by your dns provider
credentialsFile = "";
};
certs."${acmeDomain}" = {
domain = "${acmeDomain}";
};
};
networking.firewall.allowedTCPPorts = lib.mkIf add_nginx nginx_ports;
# is needed because of certificate file permissions
users.users.nginx.extraGroups = lib.mkIf add_nginx ["acme"];
services.nginx = lib.mkIf add_nginx {
upstreams."${lemmy.upstreamName}".servers."${lemmy.ip}:${builtins.toString lemmy.port}" = {};
upstreams."${lemmy-ui.upstreamName}".servers."${lemmy-ui.ip}:${builtins.toString lemmy-ui.port}" = {};
virtualHosts."${nginxVhost}" = {
useACMEHost = "${acmeDomain}";
# inherit from config.security.acme.acmeRoot;
acmeRoot = null;
# add redirects from http to https
forceSSL = true;
# this whole block was lifted from
https://github.com/LemmyNet/lemmy/blob/ef1aa18fd20cc03d492a81cb70cc75cf3281649f/docker/nginx.conf#L21 lines
21-32
extraConfig = ''
# disables emitting nginx version on error pages and in the Server response header field
server_tokens off;
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_vary on;
# Upload limit, relevant for pictrs
client_max_body_size 20M;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
'';
locations = {
"/" = {
extraConfig = ''
# distinguish between ui requests and backend
# don't change lemmy-ui or lemmy here, they refer to the upstream definitions on top
set $proxpass "http://${lemmy-ui.upstreamName}";
if ($http_accept = "application/activity+json") {
set $proxpass "http://${lemmy.upstreamName}";
}
if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
set $proxpass "http://${lemmy.upstreamName}";
}
if ($request_method = POST) {
set $proxpass "http://${lemmy.upstreamName}";
}
proxy_pass $proxpass;
# Cuts off the trailing slash on URLs to make them valid
rewrite ^(.+)/+$ $1 permanent;
# Send actual client IP upstream
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
# again, lifted wholesale from
https://github.com/LemmyNet/lemmy/blob/ef1aa18fd20cc03d492a81cb70cc75cf3281649f/docker/nginx.conf#L60 lines
60-69 (nice!)
"~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
proxyPass = "http://${lemmy.upstreamName}";
extraConfig = ''
# proxy common stuff
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
## Send actual client IP upstream
#proxy_set_header X-Real-IP $remote_addr;
#proxy_set_header Host $host;
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
};
};
};
systemd.services.lemmy-ui = {
environment = {
LEMMY_UI_HOST = lib.mkForce "${lemmy-ui.ip}:${toString lemmy-ui.port}";
LEMMY_UI_LEMMY_INTERNAL_HOST = lib.mkForce "${lemmy.ip}:${toString lemmy.port}";
LEMMY_UI_LEMMY_EXTERNAL_HOST = lib.mkForce lemmy.domain ;
LEMMY_UI_HTTPS="true";
};
};
services.pict-rs = {
enable = true;
port = pict-rs.port;
dataDir = "${dataDir}/pict-rs";
address = pict-rs.ip;
};
systemd.services.lemmy = {
requires = ["postgresql.service"];
after = ["postgresql.service"];
environment = {
LEMMY_DATABASE_URL = lib.mkForce "postgresql://lemmy@127.0.0.1:${toString
config.services.postgresql.port}/lemmy";
};
};
services.lemmy = {
enable = true;
ui.port = lemmy-ui.port;
database.createLocally = true;
settings = {
# TODO: Enable this much later when you tested everything.
# N.B. you can't change your domain name after enabling this.
federation.enabled = false;
# settings related to the postgresql database
database = {
user = "lemmy";
password = "secretlemmypassword";
host = "127.0.0.1";
port = ${config.services.postgresql.port};
database = "lemmy";
pool_size = 5;
};
# Pictrs image server configuration.
pictrs = {
# Address where pictrs is available (for image hosting)
url = "http://${pict-rs.ip}:${toString pict-rs.port}/";
# TODO: Set a custom pictrs API key. ( Required for deleting images )
api_key = "";
};
# TODO: Email sending configuration. All options except login/password are mandatory
email = {
# Hostname and port of the smtp server
smtp_server = "";
# Login name for smtp server
smtp_login = "";
# Password to login to the smtp server
smtp_password = "";
# Address to send emails from, eg "noreply@your-instance.com";
smtp_from_address = "noreply@${lemmy.domain}";
# Whether or not smtp connections should use tls. Can be none, tls, or starttls
tls_type = "none";
};
# TODO: Parameters for automatic configuration of new instance (only used at first start)
setup = {
# Username for the admin user
admin_username = "superawesomeadmin";
# Password for the admin user. It must be at least 10 characters.
admin_password = "";
# Name of the site (can be changed later)
site_name = "Lemmy at ${lemmy.domain}";
# Email for the admin user (optional, can be omitted and set later through the website)
admin_email = "admin@${lemmy.domain}";
};
# the domain name of your instance (mandatory)
hostname = lemmy.domain;
# Address where lemmy should listen for incoming requests
bind = lemmy.ip;
# Port where lemmy should listen for incoming requests
port = lemmy.port;
# Whether the site is available over TLS. Needs to be true for federation to work.
tls_enabled = true;
};
};
# needed for now
nixpkgs.config.permittedInsecurePackages = [
"nodejs-14.21.3"
"openssl-1.1.1t"
];
system.activationScripts."make_sure_lemmy_user_owns_files" = ''
uid='${config.users.users.lemmy.uid}';
gid='${config.users.groups.lemmy.gid}';
dir='${lemmy.dataDir}'
mkdir -p "''${dir}"
if [[ "$(${pkgs.toybox}/bin/stat "''${dir}" -c '%u:%g' | tee /dev/stderr )" != "''${uid}:''${gid}" ]]; then
chown -R "''${uid}:''${gid}" "''${dir}"
fi
'';
};
};
}
</div>
<div v-if="nixinServices.includes('nextcloud')">
services.nextcloud = {
enable = true;
hostName = "nextcloud.tld";
database.createLocally = true;
config = {
dbtype = "pgsql";
adminpassFile = "/path/to/admin-pass-file";
};
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
</div>
<div v-if="nixinServices.includes('hedgedoc')">
networking.firewall = {
allowedTCPPorts = [ 8001 ];
};
services.hedgedoc = {
enable = true;
settings.domain = "hedgedoc.nixin.local";
settings.port = 8001;
settings.host = "0.0.0.0";
settings.protocolUseSSL = false;
settings.allowOrigin = [
"localhost"
"hedgedoc.nixin.local"
];
};
</div>
<div v-if="nixinServices.includes('forgejoRunner')">
virtualisation.containers.enable = true;
virtualisation.podman = {
enable = true;
# Create a `docker` alias for podman, to use it as a drop-in replacement
dockerCompat = true;
# Required for containers under podman-compose to be able to talk to each other.
defaultNetwork.settings.dns_enabled = true;
};
services.gitea-actions-runner = {
package = pkgs.forgejo-runner;
instances.default = {
enable = true;
name = "dromadaire";
url = "https://git.distrilab.fr";
# Obtaining the path to the runner token file may differ
tokenFile = "/etc/forgejo/runner.token";
labels = [
# provide a debian base with nodejs for actions
"debian-latest:docker://node:20-bookworm"
# fake the ubuntu name, because node provides no ubuntu builds
"ubuntu-latest:docker://node:20-bookworm"
# nixos
"nixos:docker://nixos/nix:latest"
# provide native execution on the host
#"native:host"
];
};
};
</div>
<div v-if="nixinServices.includes('forgejo')">
services.nginx = {
virtualHosts.${cfg.settings.server.DOMAIN} = {
forceSSL = true;
enableACME = true;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/".proxyPass = "http://localhost:${toString srv.HTTP_PORT}";
};
};
services.forgejo = {
enable = true;
database.type = "postgres";
# Enable support for Git Large File Storage
lfs.enable = true;
settings = {
server = {
DOMAIN = "git.example.com";
# You need to specify this to remove the port from URLs in the web UI.
ROOT_URL = "https://${srv.DOMAIN}/";
HTTP_PORT = 3000;
};
# You can temporarily allow registration to create an admin user.
service.DISABLE_REGISTRATION = true;
# Add support for actions, based on act: https://github.com/nektos/act
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
};
# Sending emails is completely optional
# You can send a test email from the web UI at:
# Profile Picture > Site Administration > Configuration > Mailer Configuration
mailer = {
ENABLED = true;
SMTP_ADDR = "mail.example.com";
FROM = "noreply@${srv.DOMAIN}";
USER = "noreply@${srv.DOMAIN}";
};
};
mailerPasswordFile = config.age.secrets.forgejo-mailer-password.path;
};
</div>
}
</code>
</pre>

View file

@ -1,23 +0,0 @@
# Installation
## Requirements
For now, the NixiN installation process was just tested on a Linux distribution, on a computer with virtualisation capacities.
So only requirements are :
- A Linux system with virtualisation support
- With Nix installed <https://nix.dev/manual/nix/stable/installation/installation.html>
## Grab the code and run locally
Get the latest version from the official repository
```bash
git clone https://git.distrilab.fr/NixiN/nixin-web
```
Go in the main folder and run the `shell.nix` to install dependencies
```bash
cd nixin-web && nix-shell
```

View file

@ -1,32 +0,0 @@
{ config, pkgs, ... }:
{
virtualisation.vmVariant.virtualisation.forwardPorts = [
{ from = "host"; host.port = 8001; guest.port = 8001; }
];
networking.hosts = {
"127.0.0.1" = [ "hedgedoc.nixin.local" ];
};
networking.hostName = "demo";
#networking.firewall.enable = false;
networking.firewall = {
allowedTCPPorts = [ 8001 ];
};
services.hedgedoc = {
enable = true;
settings.domain = "hedgedoc.nixin.local";
settings.port = 8001;
settings.host = "0.0.0.0";
settings.protocolUseSSL = false;
settings.allowOrigin = [
"localhost"
"hedgedoc.nixin.local"
];
};
users.users.operator = {
isNormalUser = true;
extraGroups = [ "wheel" ];
initialPassword = "test";
};
system.stateVersion = "24.05";
}

4
package-lock.json generated
View file

@ -1,11 +1,9 @@
{
"name": "NixiN",
"name": "nixin-web",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "NixiN",
"license": "AGPL-3.0",
"devDependencies": {
"vitepress": "^1.3.3"
}

View file

@ -1,11 +1,4 @@
{
"name": "NixiN",
"repository": {
"type": "git",
"url": "git+https://git.distrilab.fr/NixiN/nixin-web.git"
},
"license": "AGPL-3.0",
"homepage": "https://nixin.distrilab.eu",
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",

View file

@ -1,14 +0,0 @@
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "nixin";
buildInputs = [
nodejs # for vitepress
nixos-generators # for building images
];
shellHook = ''
export PATH="$PWD/node_modules/.bin/:$PATH"
npm install
npm run
'';
}

View file

@ -1,36 +1,28 @@
# Technical principles
ToDo: intro on best practices that drive the technical choices
## KISS principle
"Keep it simple, stupid!"
## Do not reinvent the wheel
ToDo: nixos
ToDo: passwordstore
ToDo: krops
## There is only one timezone
Experience has shown that using multiple time-zones for the servers of an infrastructure is a recipe for disaster.
Also, using the timezone of one country for an international project is a source of confusion and headaches.
Especially when that timezone is subject to daylight saving changes that are causing the clock to jump 1 hour forward or backward twice a year.
The only sensible choice is to set the servers time to UTC and to transalte the timestamps to the user's timezone when displaying them on an interface.
The only sensible choice is to set the servers time to UTC and to transalte the timestamps to the user's timezone when displaying them.
This is strongly opinion based. We may not not all agree on the subject. This is why we will make sure that it is easy for the users to choose their prefered timezone for setting up their servers.
This is strongly opinion based. And we may not not all agree on the subject. This is why we will make sure that it is easy for the users to choose their prefered timezone for setting up their servers.
## Eat your own dog food
The project is bootstrapped using an infrastructure that is based on Proxmox, Debian and YunoHost for hosting its website and git forge.
## Eat your own food
The project is bootstrapped using a hosting infrastructure thagtis based on Proxmox, Debian and YunoHost.
But the goal is to host the project using itself as soon as possible. That is using NixOS servers managed with the tools and principles developed by the NixiN project.
Currently only the forgejo action runners used for CI/CD are hosted on NixOS servers.
But the goal is to host the whole project using itself as soon as possible. That is using NixOS servers managed with the tools and principles developed within the NixiN project.
## CI/CD
## Focus on user experience
## Prioritize security
ToDo: only open ports that are strictily necessary on the public interface. go through a VPN for everything else
ToDo: use fail2ban or reaction
ToDo: passwords manager
## No premature performance optimization
Use best practices to write efficient code but do not write overly complicated solutions based on a-priori thinking of performance issue.
@ -40,9 +32,3 @@ Only optimize what has been tested to be an issue.
Even though we think that Rust would be a better language for developing the tools of the project we are starting the first version using Go because it is faster to develop with it and easier to find contributors with this languages.
## ToDo
favor modern filesystems with snapshoting capability like zfs and btrfs
## To flake or not to flake?
There is a bit of controversy around flakes. They bring some intereting convenience when using NixOS and have spawned an extensive ecosystem. But they are not without drawbacks. We have decided to not use flakes for now. But we'll keep our architecture open for the users who want to use them.