In this blog post you are going to see how to make simple determenistic application with the power of Nix.
First of all, no offence to Luca Bruno aka Lethalman - the inventor of Nix pills
Second of all, do not worry, Nix pills are not actual pills.
There was a joke a month back … about an app that shows system usage of remote computer in your system tray or something like that… well I made it. Khm… apperantly all my apps start with a joke.
Get the code from github:
This will build the app to Nix store:
To run the app, just execute in current folder:
To use it on remote machine you have to forward
port (from example).1
8080/tcp
To test if it is working on remote machine use following command:
Or just open in browser the following page:
, the browser should complain about unsecure connection but this is just because the cert is self-signed. After that you will have to enter username and password.1
https://<yourip>:8080/
You could try also other scripts .. like
or 1
https://<yourip>:8080/hello.pl
.1
https://<yourip>:8080/hello.py
The program is in one short file, lets go through it:
At the start we declare parameters and its default values:
1
server
block1
2
3
4
5
6
7
8
9
{ pkgs ? import <nixpkgs> {}
, prefix ? "/var/lib/cgisysinfo"
, listenAddress ? "localhost"
, listenPort ? "9999"
, user ? "user"
, password ? "password"
, templatesFile ? "${prefix}/templates.nix"
, extraNginxConf ? "" }:
let
Now we have to create a
- the web server configuration with wich we are going serve cgi scripts.1
nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
nginxconf = pkgs.writeText "nginx.conf" ''
pid ${prefix}/nginx.pid;
worker_processes 1;
events {
worker_connections 128;
}
http {
server {
access_log ${prefix}/cgi.access.log;
error_log ${prefix}/cgi.error.log;
ssl on;
ssl_certificate ${prefix}/ssl/selfsigned.crt;
ssl_certificate_key ${prefix}/ssl/selfsigned.key;
root ${scripts}/www;
index index.sh index.pl index.py;
listen ${listenAddress}:${listenPort};
location ~ .(py|pl|sh)$ {
expires -1;
auth_basic "closed site";
auth_basic_user_file ${htpasswd};
gzip off;
fastcgi_pass unix:${prefix}/fcgiwrap.socket;
# include fastcgi_params;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
}
${extraNginxConf}
}
}
'';
Set variable
for 1
socketPath
and create file 1
fcgiwrap.socket
for simple authentication for Nginx (when built, password will be hashed - there will be no plain text password in store).1
htpasswd
1
2
3
4
5
6
7
8
9
10
socketPath = "${prefix}/fcgiwrap.socket";
htpasswd = pkgs.stdenv.mkDerivation {
name = "${user}-htpasswd";
phases = "installPhase";
installPhase = ''
export PATH="${pkgs.openssl}/bin:$PATH"
printf "${user}:$(openssl passwd -crypt ${password})\n" >> $out
'';
};
Here comes a bit of magic, we take
which we are going to see later and make python/perl/bash scripts from it.1
templatesFile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scripts =
let
templates = import templatesFile {inherit pkgs prefix;};
paths = map (template:
pkgs.writeTextFile rec {
name = "cgisysinfo-${template.name}";
text = template.text;
executable = true;
destination = "/www/${template.name}";
}) templates;
in
pkgs.buildEnv {
name = "cgisysinfo-scripts";
inherit paths;
pathsToLink = [ "/www" ];
};
Main run script takes care of starting
and 1
fcgiwrap
.1
Nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
run = pkgs.writeScriptBin "cgisysinfo-run" ''
#!${pkgs.bash}/bin/bash
# we send QUIT signal to Nginx when you press Ctrl+C
function stopall() {
kill -QUIT $( cat ${prefix}/nginx.pid )
exit
}
trap "stopall" INT
export PATH="${pkgs.fcgiwrap}/sbin:${pkgs.nginx}/bin:${pkgs.openssl}/bin:$PATH"
# generate self-signed cert for nginx (if folder 'ssl' does not exist yet)
test -d ${prefix}/ssl || \
{ mkdir -p ${prefix}/ssl && \
openssl req -new -x509 -nodes -keyout ${prefix}/ssl/selfsigned.key -out ${prefix}/ssl/selfsigned.crt -subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.xyz"; }
test -S ${socketPath} && unlink ${socketPath}
echo -e "\nExample usage:"
echo "$ notify-send \"\`curl --user ${user}:<your-password> -k https://${listenAddress}:${listenPort}/\`\""
echo -e "\nPress Ctrl+C to stop ..."
# start nginx as daemon
mkdir -p ${prefix}/var/logs
nginx -c ${nginxconf} -p ${prefix}/var
# start fcgiwrap (this one blocks)
fcgiwrap -c 1 -s unix:${socketPath}
'';
At the end we install
command to Nix store.
I also added a 1
cgisysinfo-run
to run app with 1
shellHook
.1
nix-shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cgisysinfo = pkgs.stdenv.mkDerivation rec {
name = "cgisysinfo-${version}";
version = "0.2";
unpackPhase = "true";
installPhase = ''
mkdir -p $out/bin
ln -s ${run}/bin/* $out/bin
'';
shellHook = ''
${run}/bin/cgisysinfo-run
'';
};
in cgisysinfo
1
templatesFile
This is an example of
which is also located on github as example. Currently only 1
templates.nix
, 1
.py
and 1
.pl
files are supported.1
.sh
Ok lets make one thing clear at this point, for security reasons, be very carefull what information and how you expose on the internet, this is literally remote code execution app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{ pkgs, prefix }:
[
{
name = "index.sh";
text = ''
#!${pkgs.bash}/bin/bash
export PATH="$PATH:${pkgs.procps}/bin:${pkgs.sysstat}${pkgs.sysstat}/bin"
echo -e "Content-type: text/plain\n\n"
echo RAM: `free -mh | awk 'NR==2{ print $3"/"$2 }'`
echo Swap: `free -mh | awk 'NR==3{ print $3"/"$2 }'`
ps -eo pcpu,pmem,user,args | sort -k 1 -r | awk 'NR>1 && NR<5{n=split($4,a,"/"); print a[n]": cpu:"$1"%, mem:"$2"%, u:"$3}'
echo
'';
}
{
name = "hello.pl";
text = ''
#!${pkgs.perl}/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>Hello, Perl world.</body></html>";
'';
}
{
name = "hello.py";
text = ''
#!${pkgs.python27}/bin/python
print "Content-type: text/html\n\n";
print "<html><body>Hello, Python world.</body></html>";
'';
}
]
You have seen how easy is to make a Nix application. This is it. Happy hacking!