mirror of
https://github.com/archtechx/nix.git
synced 2025-12-12 03:24:02 +00:00
Add static.nix, add wwwRedirect, simplify mkUsername (#6)
This commit is contained in:
parent
4bc7ebaf4c
commit
bc8ad1fd71
3 changed files with 208 additions and 23 deletions
47
README.md
47
README.md
|
|
@ -170,9 +170,20 @@ Simply `scp laravel.nix root@<your server ip>:/etc/nixos/` and start writing con
|
||||||
|
|
||||||
### www redirects
|
### www redirects
|
||||||
|
|
||||||
The module doesn't handle www redirects automatically. This may be added in the future.
|
To redirect `www.acme.com` to `acme.com`, you can use the `wwwRedirect` attribute. It should be
|
||||||
|
null for no redirect, or an integer status code for an enabled redirect.
|
||||||
|
|
||||||
At this time, I'd recommend handling basic redirects like that on Cloudflare.
|
```nix
|
||||||
|
(laravelSite {
|
||||||
|
name = "foo";
|
||||||
|
domains = [ "foo.com" ]
|
||||||
|
wwwRedirect = 301; # permanent redirect
|
||||||
|
# ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
With the config above, `www.foo.com/bar` will return a redirect to `foo.com/bar`, with the schema
|
||||||
|
matching the site's `ssl` config.
|
||||||
|
|
||||||
### Default nginx server
|
### Default nginx server
|
||||||
|
|
||||||
|
|
@ -299,6 +310,38 @@ curl -s https://www.cloudflare.com/ips-v4 | sha256 | xargs nix hash convert --ha
|
||||||
curl -s https://www.cloudflare.com/ips-v6 | sha256 | xargs nix hash convert --hash-algo sha256 --to nix32
|
curl -s https://www.cloudflare.com/ips-v6 | sha256 | xargs nix hash convert --hash-algo sha256 --to nix32
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Static sites
|
||||||
|
|
||||||
|
For hosting static sites, you can use `static.nix` very similarly to `laravel.nix`. Notable differences:
|
||||||
|
1. `root` is required, e.g. `name="foo"; root="build";` means `/srv/foo/build` will be served. In other
|
||||||
|
words, even though this is for static sites, we do not serve the entire `/srv/{name}` dir to allow
|
||||||
|
for version control and build steps.
|
||||||
|
2. By default, the `static-generic` user is used. Static sites do not always need strict user separation
|
||||||
|
since there's no request runtime. That said, the user is *very* limited and only has `pkgs.git` and
|
||||||
|
`pkgs.unzip`. Therefore it's only suited for static sites that are at most pulled from somewhere,
|
||||||
|
rather than built using Node.js. Also note that GitHub generally doesn't allow using a single SSH key
|
||||||
|
as the deploy key on multiple repos. For these reasons, it's still recommended to enable user creation
|
||||||
|
via `user = true;`.
|
||||||
|
|
||||||
|
Full usage:
|
||||||
|
```nix
|
||||||
|
(staticSite {
|
||||||
|
name = "foo"; # name of the site
|
||||||
|
root = "build"; # directory within /srv/foo to be served by nginx
|
||||||
|
|
||||||
|
user = true; # if false, static-generic is used. Default: false
|
||||||
|
domains = [ "foo.com" "bar.com" ]; # domains to serve the site on
|
||||||
|
ssl = true; # enableACME + forceSSL. Default: false
|
||||||
|
# Status code for www-to-non-www redirects. No redirect if null. Applies to all sites
|
||||||
|
wwwRedirect = 301; # Default: null
|
||||||
|
cloudflareOnly = true; # use Authenticated Origin Pulls. See the dedicated section. Default: false
|
||||||
|
extraPackages = [ pkgs.nodejs_24 ]; # only applies if user=true
|
||||||
|
generateSshKey = true; # defaults to true, used even with user=false
|
||||||
|
sshKeys = [ "array" "of" "public" "ssh" "keys" ]; # optional
|
||||||
|
extraNginxConfig = "nginx configuration string"; # optional
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|
||||||
It's a good idea to have `/etc/nixos` tracked in version control so you can easily revert the config
|
It's a good idea to have `/etc/nixos` tracked in version control so you can easily revert the config
|
||||||
|
|
|
||||||
52
laravel.nix
52
laravel.nix
|
|
@ -3,6 +3,7 @@
|
||||||
phpPackage, # e.g. pkgs.php84
|
phpPackage, # e.g. pkgs.php84
|
||||||
domains ? [], # e.g. [ "example.com" "acme.com" ]
|
domains ? [], # e.g. [ "example.com" "acme.com" ]
|
||||||
ssl ? false, # Should SSL be used
|
ssl ? false, # Should SSL be used
|
||||||
|
wwwRedirect ? null, # The status code used for www-to-non-www redirects. Null means no redirect
|
||||||
cloudflareOnly ? false, # Should CF Authenticated Origin Pulls be used
|
cloudflareOnly ? false, # Should CF Authenticated Origin Pulls be used
|
||||||
extraNginxConfig ? null, # Extra nginx config string
|
extraNginxConfig ? null, # Extra nginx config string
|
||||||
sshKeys ? null, # SSH public keys used to log into the site's user for deployments
|
sshKeys ? null, # SSH public keys used to log into the site's user for deployments
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
mkUsername = siteName: "laravel-${siteName}";
|
username = "laravel-${name}";
|
||||||
in {
|
in {
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
security.acme.acceptTerms = lib.mkIf ssl true;
|
security.acme.acceptTerms = lib.mkIf ssl true;
|
||||||
|
|
@ -41,13 +42,14 @@ in {
|
||||||
|
|
||||||
# Create welcome message for user
|
# Create welcome message for user
|
||||||
# todo: the created /etc file should ideally be 0750
|
# todo: the created /etc file should ideally be 0750
|
||||||
|
# Note: keep in sync with static.nix
|
||||||
environment.etc."laravel-${name}-bashrc".text = ''
|
environment.etc."laravel-${name}-bashrc".text = ''
|
||||||
export PATH="$HOME/.config/composer/vendor/bin/:$PATH"
|
export PATH="$HOME/.config/composer/vendor/bin/:$PATH"
|
||||||
|
|
||||||
# Laravel site welcome message
|
# Laravel site welcome message
|
||||||
echo "Welcome to ${name} Laravel site!"
|
echo "Welcome to ${name} Laravel site!"
|
||||||
echo "Domains: ${lib.concatStringsSep ", " domains}"
|
echo "Domains: ${lib.concatStringsSep ", " domains}"
|
||||||
echo "User home: /home/${mkUsername name}"
|
echo "User home: /home/${username}"
|
||||||
echo "Site: /srv/${name}"
|
echo "Site: /srv/${name}"
|
||||||
echo "Restart php-fpm: sudo systemctl reload phpfpm-${name}"
|
echo "Restart php-fpm: sudo systemctl reload phpfpm-${name}"
|
||||||
${lib.optionalString queue ''echo "Restart queue: php artisan queue:restart"''}
|
${lib.optionalString queue ''echo "Restart queue: php artisan queue:restart"''}
|
||||||
|
|
@ -60,12 +62,12 @@ in {
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /srv 0751 root root - -"
|
"d /srv 0751 root root - -"
|
||||||
"d /home 0751 root root - -"
|
"d /home 0751 root root - -"
|
||||||
"d /srv/${name} 0750 ${mkUsername name} ${mkUsername name} - -"
|
"d /srv/${name} 0750 ${username} ${username} - -"
|
||||||
"C /home/${mkUsername name}/.bashrc 0640 ${mkUsername name} ${mkUsername name} - /etc/laravel-${name}-bashrc"
|
"C /home/${username}/.bashrc 0640 ${username} ${username} - /etc/laravel-${name}-bashrc"
|
||||||
];
|
];
|
||||||
|
|
||||||
services.cron.systemCronJobs = [
|
services.cron.systemCronJobs = [
|
||||||
"* * * * * ${mkUsername name} cd /srv/${name} && ${phpPackage}/bin/php artisan schedule:run > /dev/null 2>&1"
|
"* * * * * ${username} cd /srv/${name} && ${phpPackage}/bin/php artisan schedule:run > /dev/null 2>&1"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Laravel queue worker service
|
# Laravel queue worker service
|
||||||
|
|
@ -75,8 +77,8 @@ in {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
User = mkUsername name;
|
User = username;
|
||||||
Group = mkUsername name;
|
Group = username;
|
||||||
WorkingDirectory = "/srv/${name}";
|
WorkingDirectory = "/srv/${name}";
|
||||||
ExecStart = "${phpPackage}/bin/php artisan queue:work ${queueArgs}";
|
ExecStart = "${phpPackage}/bin/php artisan queue:work ${queueArgs}";
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
|
|
@ -88,8 +90,9 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
# SSH key generation for git deployments
|
# SSH key generation for git deployments
|
||||||
|
# Note: keep in sync with static.nix
|
||||||
systemd.services."generate-ssh-key-${name}" = lib.mkIf generateSshKey {
|
systemd.services."generate-ssh-key-${name}" = lib.mkIf generateSshKey {
|
||||||
description = "Generate SSH key for ${mkUsername name}";
|
description = "Generate SSH key for ${username}";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "users.target" ];
|
after = [ "users.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
|
@ -98,15 +101,15 @@ in {
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
script = ''
|
script = ''
|
||||||
USER_HOME="/home/${mkUsername name}"
|
USER_HOME="/home/${username}"
|
||||||
SSH_DIR="$USER_HOME/.ssh"
|
SSH_DIR="$USER_HOME/.ssh"
|
||||||
KEY_FILE="$SSH_DIR/id_ed25519"
|
KEY_FILE="$SSH_DIR/id_ed25519"
|
||||||
|
|
||||||
if [[ ! -f "$KEY_FILE" ]]; then
|
if [[ ! -f "$KEY_FILE" ]]; then
|
||||||
echo "Generating SSH key for ${mkUsername name}"
|
echo "Generating SSH key for ${username}"
|
||||||
mkdir -p "$SSH_DIR"
|
mkdir -p "$SSH_DIR"
|
||||||
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f "$KEY_FILE" -N "" -C "${mkUsername name}"
|
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f "$KEY_FILE" -N "" -C "${username}"
|
||||||
chown -R ${mkUsername name}:${mkUsername name} "$SSH_DIR"
|
chown -R ${username}:${username} "$SSH_DIR"
|
||||||
chmod 700 "$SSH_DIR"
|
chmod 700 "$SSH_DIR"
|
||||||
chmod 600 "$KEY_FILE"
|
chmod 600 "$KEY_FILE"
|
||||||
chmod 640 "$KEY_FILE.pub"
|
chmod 640 "$KEY_FILE.pub"
|
||||||
|
|
@ -114,7 +117,7 @@ in {
|
||||||
echo "Public key for deploy key:"
|
echo "Public key for deploy key:"
|
||||||
cat "$KEY_FILE.pub"
|
cat "$KEY_FILE.pub"
|
||||||
else
|
else
|
||||||
echo "SSH key already exists for ${mkUsername name}"
|
echo "SSH key already exists for ${username}"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
@ -169,11 +172,18 @@ in {
|
||||||
deny all;
|
deny all;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
}) // lib.optionalAttrs (wwwRedirect != null) (lib.genAttrs (map (domain: "www.${domain}") domains) (wwwDomain: {
|
||||||
|
enableACME = ssl;
|
||||||
|
forceSSL = ssl;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
return = "${toString wwwRedirect} ${if ssl then "https" else "http"}://${lib.removePrefix "www." wwwDomain}$request_uri";
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
# PHP-FPM pool configuration
|
# PHP-FPM pool configuration
|
||||||
services.phpfpm.pools.${name} = {
|
services.phpfpm.pools.${name} = {
|
||||||
user = mkUsername name;
|
user = username;
|
||||||
phpPackage = phpPackage;
|
phpPackage = phpPackage;
|
||||||
settings = poolSettings // extraPoolSettings // {
|
settings = poolSettings // extraPoolSettings // {
|
||||||
"listen.owner" = config.services.nginx.user;
|
"listen.owner" = config.services.nginx.user;
|
||||||
|
|
@ -181,11 +191,11 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
# User and group settings
|
# User and group settings
|
||||||
users.users.${mkUsername name} = {
|
users.users.${username} = {
|
||||||
group = mkUsername name;
|
group = username;
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
createHome = true;
|
createHome = true;
|
||||||
home = "/home/${mkUsername name}";
|
home = "/home/${username}";
|
||||||
homeMode = "750";
|
homeMode = "750";
|
||||||
shell = pkgs.bashInteractive;
|
shell = pkgs.bashInteractive;
|
||||||
packages = [ phpPackage pkgs.git pkgs.unzip phpPackage.packages.composer ] ++ extraPackages;
|
packages = [ phpPackage pkgs.git pkgs.unzip phpPackage.packages.composer ] ++ extraPackages;
|
||||||
|
|
@ -193,14 +203,14 @@ in {
|
||||||
openssh.authorizedKeys.keys = sshKeys;
|
openssh.authorizedKeys.keys = sshKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.${mkUsername name} = {};
|
users.groups.${username} = {};
|
||||||
|
|
||||||
# Add site group to nginx service
|
# Add site group to nginx service
|
||||||
systemd.services.nginx.serviceConfig.SupplementaryGroups = [ (mkUsername name) ];
|
systemd.services.nginx.serviceConfig.SupplementaryGroups = [ username ];
|
||||||
|
|
||||||
# Sudo rules for service management
|
# Sudo rules for service management
|
||||||
security.sudo.extraRules = [{
|
security.sudo.extraRules = [{
|
||||||
users = [ (mkUsername name) ];
|
users = [ username ];
|
||||||
commands = [
|
commands = [
|
||||||
{
|
{
|
||||||
command = "/run/current-system/sw/bin/systemctl reload phpfpm-${name}";
|
command = "/run/current-system/sw/bin/systemctl reload phpfpm-${name}";
|
||||||
|
|
|
||||||
132
static.nix
Normal file
132
static.nix
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
{
|
||||||
|
name, # Name of the site, /srv/{name} will be based on this as well as the username if user=true
|
||||||
|
root, # The directory within /srv/{name} that should be served by nginx
|
||||||
|
user ? false, # Should a user be created. If false, static-generic is used
|
||||||
|
domains ? [], # e.g. [ "example.com" "acme.com" ]
|
||||||
|
ssl ? false, # Should SSL be used
|
||||||
|
wwwRedirect ? null, # The status code used for www-to-non-www redirects. Null means no redirect
|
||||||
|
cloudflareOnly ? false, # Should CF Authenticated Origin Pulls be used
|
||||||
|
extraNginxConfig ? null, # Extra nginx config string
|
||||||
|
sshKeys ? null, # SSH public keys used to log into the site's user for deployments
|
||||||
|
extraPackages ? [], # Any extra packages the user should have in $PATH (only used with user=true)
|
||||||
|
generateSshKey ? true, # Generate an SSH key for the user (used for GH deploy keys)
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
username = if user then "static-${name}" else "static-generic";
|
||||||
|
in {
|
||||||
|
services.nginx.enable = true;
|
||||||
|
security.acme.acceptTerms = true;
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 ] ++ lib.optionals ssl [ 443 ];
|
||||||
|
|
||||||
|
services.nginx.virtualHosts = lib.genAttrs domains (domain: {
|
||||||
|
enableACME = ssl;
|
||||||
|
forceSSL = ssl;
|
||||||
|
root = "/srv/${name}/${root}";
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
${lib.optionalString cloudflareOnly ''
|
||||||
|
ssl_verify_client on;
|
||||||
|
ssl_client_certificate ${pkgs.fetchurl {
|
||||||
|
url = "https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem";
|
||||||
|
sha256 = "0hxqszqfzsbmgksfm6k0gp0hsx9k1gqx24gakxqv0391wl6fsky1";
|
||||||
|
}};
|
||||||
|
''}
|
||||||
|
${lib.optionalString (extraNginxConfig != null) extraNginxConfig}
|
||||||
|
'';
|
||||||
|
|
||||||
|
locations = {
|
||||||
|
"= /favicon.ico".extraConfig = ''
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
'';
|
||||||
|
|
||||||
|
"= /robots.txt".extraConfig = ''
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}) // lib.optionalAttrs (wwwRedirect != null) (lib.genAttrs (map (domain: "www.${domain}") domains) (wwwDomain: {
|
||||||
|
enableACME = ssl;
|
||||||
|
forceSSL = ssl;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
return = "${toString wwwRedirect} ${if ssl then "https" else "http"}://${lib.removePrefix "www." wwwDomain}$request_uri";
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /srv 0751 root root - -"
|
||||||
|
"d /home 0751 root root - -"
|
||||||
|
"d /srv/${name} 0750 ${username} ${username} - -"
|
||||||
|
] ++ lib.optional user
|
||||||
|
"C /home/${username}/.bashrc 0640 ${username} ${username} - /etc/static-${name}-bashrc";
|
||||||
|
|
||||||
|
# User and group settings
|
||||||
|
users.users.${username} = {
|
||||||
|
group = username;
|
||||||
|
isSystemUser = true;
|
||||||
|
createHome = true;
|
||||||
|
home = "/home/${username}";
|
||||||
|
homeMode = "750";
|
||||||
|
shell = pkgs.bashInteractive;
|
||||||
|
packages = [ pkgs.git pkgs.unzip ] ++ lib.optionals user extraPackages;
|
||||||
|
} // lib.optionalAttrs (sshKeys != null && user) {
|
||||||
|
openssh.authorizedKeys.keys = sshKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.${username} = {};
|
||||||
|
|
||||||
|
# Add site group to nginx service
|
||||||
|
systemd.services.nginx.serviceConfig.SupplementaryGroups = [ username ];
|
||||||
|
|
||||||
|
# SSH key generation for git deployments
|
||||||
|
# Note: keep in sync with laravel.nix
|
||||||
|
# Unlike laravel.nix, the key here includes username, not the site name since static-generic
|
||||||
|
# can be used for multiple sites
|
||||||
|
systemd.services."generate-ssh-key-${username}" = lib.mkIf generateSshKey {
|
||||||
|
description = "Generate SSH key for ${username}";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "users.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
User = "root";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
USER_HOME="/home/${username}"
|
||||||
|
SSH_DIR="$USER_HOME/.ssh"
|
||||||
|
KEY_FILE="$SSH_DIR/id_ed25519"
|
||||||
|
|
||||||
|
if [[ ! -f "$KEY_FILE" ]]; then
|
||||||
|
echo "Generating SSH key for ${username}"
|
||||||
|
mkdir -p "$SSH_DIR"
|
||||||
|
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f "$KEY_FILE" -N "" -C "${username}"
|
||||||
|
chown -R ${username}:${username} "$SSH_DIR"
|
||||||
|
chmod 700 "$SSH_DIR"
|
||||||
|
chmod 600 "$KEY_FILE"
|
||||||
|
chmod 640 "$KEY_FILE.pub"
|
||||||
|
echo "SSH key generated: $KEY_FILE.pub"
|
||||||
|
echo "Public key for deploy key:"
|
||||||
|
cat "$KEY_FILE.pub"
|
||||||
|
else
|
||||||
|
echo "SSH key already exists for ${username}"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Create welcome message for user
|
||||||
|
# Note: keep in sync with laravel.nix (same block, minor changes here)
|
||||||
|
environment.etc."static-${name}-bashrc" = lib.mkIf user {
|
||||||
|
text = ''
|
||||||
|
echo "Welcome to ${name} static site!"
|
||||||
|
echo "Domains: ${lib.concatStringsSep ", " domains}"
|
||||||
|
echo "User home: /home/${username}"
|
||||||
|
echo "Site: /srv/${name}"
|
||||||
|
${lib.optionalString generateSshKey ''echo "SSH public key: cat ~/.ssh/id_ed25519.pub"''}
|
||||||
|
echo "---"
|
||||||
|
'';
|
||||||
|
} ;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue