This repository contains a prototype application. What we hear about
"prototype" is written in commence
's
README.
In particular, in addition of working features, we want
a software artifact that documents how the real software should work, documents what the business logic is, and provides confidence in the business logic by virtue of having a working implementation.
A demonstration instance of Curiosity is running at cty-2.hypered.systems. It contains documentation that complements this README. If you're non-technical, those links are a better starting point. The rest of this README is intended for more technical profiles.
A previous version of Curiosity, written for Smart Coop, is running at cty-1.hypered.systems.
Curiosity offers multiple tools compiled as a single executable, called
cty
. The main command, cty serve
, is a web application. In
particular it uses the servant
and stm
libraries. The stm
library is used
instead of a regular relational database (e.g. PostgreSQL). This means the
whole state of the application is in memory instead of a "real" database.
In addition of the web application, some other commands are provided to better demonstrate and explore the features of the application.
cty serve
runs the web application.cty repl
is similar but exposes a REPL instead of an HTTP server to run commands and interact with the state.cty run
interprets scripts containing commands (the same commands that the REPL uses).cty sock
offers a text interface similar tocty repl
but through a UNIX-domain socket, and accepts multiple clients.- Other commands of
cty
are meant to be a client for thecty sock
server. It can also be run against a local state file. cty parse
is a helper command to play with the command parser used incty repl
.
Note: The commands that can be typed in the REPL are using the same syntax
used by cty
itself. E.g. cty state
typed in a shell, is similar to state
typed in the Curiosity REPL.
-
The prototype uses
design-hs
as a component library to create HTML pages. -
It also uses
commence
as a kind of base "framework" to organize the prototype.
Tests covering (partially) the library can be run by entering the Nix shell and
using cabal
:
$ nix-shell
$ cabal test --test-show-details=direct
The same thing can be accomplished with a convenience script:
scripts/run-tests.sh
, which takes care of entering the Nix shell.
The test suite uses hspec
, so its command-line
options apply.
In GHCi, two convenience functions are defined: runSpec
and runScenarios
.
Integration tests, which use the Nix testing infrastructure, can be run by building a specific attribute (see below for an interactive environment):
$ nix-build -A run-vm-tests
A GitHub action is used to run the tests on every commit, and report the results on each Pull Request. It is also used to populate a Nix binary cache.
Reloading the code automatically upon changes can be done with ghcid
. A
convenience script to run ghcid
is script/ghcid.sh
.
ghcid
can also be instructed to run something when code loads successfully.
This is used to typically run tests, but can also be used to restart the
Curiosity HTTP server.
This looks like this (be sure to enter the nix-shell
first):
$ ghcid --warnings --command scripts/ghci.sh --test ':main serve'
Note: use the
autoReload
function defined in Curiosity.Html.Misc
to cause an open web page to be
automatically refreshed when working on some HTML snippet.
The same mechanism can be used with the HSpec-based tests or the Golden tests:
$ ghcid --warnings --command scripts/ghci-spec.sh --test ':main'
$ ghcid --warnings --command scripts/ghci-scenarios.sh --test ':main'
There are multiple ways to run Curiosity's code. Building the cty
binary and
executing it is only one way.
It is possible to load the code in GHCi, so that it is interpreted instead of
compiled. This provides a quick way to reload the code (with :r
).
One way is to use cabal
directly, but this exposes the Curiosity.Command
module, instead of Main
:
$ nix-shell
$ cabal repl curiosity
Instead of using cabal
, it's possible to use a helper script to call :main
within GHCi.
$ script/ghci.sh
ghci> :main --help
We can also use Cabal to run the example. The advantage compared to using Nix to build the binary is that it is incremental (i.e. re-use already compiled but not modified modules):
$ nix-shell
$ cabal run -- cty --help
(If necessary, the built executable is at
dist-newstyle/build/x86_64-linux/ghc-9.0.2/curiosity-0.1.0.0/x/cty/build/cty/cty
.)
The binary can be built and run also this way, which bypass building the tests:
$ cabal build curiosity
$ dist-newstyle/build/x86_64-linux/ghc-9.0.2/curiosity-0.1.0.0/x/cty/build/cty/cty --help
We can build a binary with Nix:
$ nix-build -A binaries
$ result/bin/cty --help
Finally, some environments also contain a runnable executable Curiosity.
We can have a shell populated with the Curiosity binaries and man pages with:
$ nix-shell default.nix -A shell
$ cty --help
$ man cty
That shell also provides a run-full-environment
script. It runs a local
environment where cty serve
and an Nginx reverse proxy are running together:
$ run-full-environment
This is the closest environment we have that mimicks the complete virtual machine image without actually running a VM. It is based on a procfile, run by hivemind.
It is also possible to only run Nginx in that shell with run-nginx
. This is
useful for instance to proxy a cty serve
running from GHCi.
The above script is simply a tiny wrapper around the run
attribute:
$ nix-build -A run
$ result/bin/run-full-environment
The run-vm-tests
attribute lets you run tests based on a NixOS VM mirroring
the production setup, plus a client VM. You can run it either
non-interactively...:
$ nix-build -A run-vm-tests
... or interactively:
$ nix-build -A run-vm-tests.driverInteractive
$ result/bin/nixos-test-driver
> start_all( )
This opens two QEMU windows, one for the server, one for the client, and you
can use the root account to interactively log in the VMs. The client can access
the server using the cty-2.hypered.systems
domain name. You can read the relevant
NixOS manual
section
for more informations.
Note: these VMs are not connected to the internet, cty-2.hypered.systems
here
refers to the server VM, not the production machine.
And finally, we can run a local virtual machine running both cty serve
and an
Nginx reverse proxy using QEMU (see below):
$ nix-build -A runvm
$ result/bin/run-nixos-vm
The web application can be accessed at 127.0.0.1:8180
. A helper script is
provided to do the same: scripts/runvm.sh
.
The virtual machine image running at cty-2.hypered.systems
is based on the above, and
can be built with:
$ nix-build -A image
cty
is the main command-line tool to run the web application, interact
against a server running on the same host (through a UNIX-domain socket), or
against a state file. Note that modifying a state file used by a running server
should be avoided.
By default, cty
interacts against a state file called state.json
. Use the
--state
option to override the file name. Use the --socket
option to
instead interact against a running server.
$ cty init
State file 'state.json' created.
$ cty state
{...}
$ cty user get alice
No such user.
$ cty user signup alice secret [email protected] --accept-tos
User created: USER-1
Signup confirmation email enqueued: EMAIL-1
$ cty user get USER-1
{...}
The REPL can be quit with the quit
command, or Ctrl-d
:
$ cty repl
> quit
The REPL accepts commands similar to the cty
sub-commands, including
--help
.
> user signup alice secret [email protected] --accept-tos
> --help
Scripts (i.e. files containing commands) can be run with cty run
. Exampe
scripts can be found within the repository, in the scenarios/
directory.
$ cty run scenarios/user-signup.txt
1: reset
State is now empty.
3: user signup alice secret [email protected] --accept-tos
User created: USER-1
Signup confirmation email enqueued: EMAIL-1
4: user get USER-1 --short
USER-1 alice
5: quit
Exiting
Such scripts, together with their expected output, are used as a high-level testing mechanism.
You can re-use the Curiosity binaries built by the CI through a custom Nix binary cache.
On a NixOS system add the following snippet to your system configuration:
nix.settings = {
substituters = [ "https://s3.eu-central-003.backblazeb2.com/curiosity-store/" "https://cache.nixos.org/" ];
trusted-public-keys = [ "curiosity-store:W3LXUB+6DjtZkKV0gEfNXGtTjA+hMqjPUoK6mzzco+w=" "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
}
On a non-NixOS system, you can edit the /etc/nix/nix.conf
file and set the
substituters
and trusted-public-keys
configuration attributes to:
substituters = https://s3.eu-central-003.backblazeb2.com/curiosity-store/ https://cache.nixos.org/
trusted-public-keys = curiosity-store:W3LXUB+6DjtZkKV0gEfNXGtTjA+hMqjPUoK6mzzco+w= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
A Docker image can be built to experiment with the cty
program, and have
access to man pages. By default, running the image will run Bash.
$ nix-build -A docker
$ docker load < result
$ docker run --privileged -it -p 9000:9000 curiosity:vsj9an994jjrw47kv9fj0icjs2f6xq6r
Note: the image tag will be different as it depends on the actual image content.
Note: -p 9000:9000
is necessary only if you want to run the web
applicatio with cty serve
.
A virtual machine image that can be run with QEMU can be built and run with:
$ nix-build -A runvm
$ result/bin/run-nixos-vm
Within the VM, Nginx is setup as a reverse-proxy in front of cty serve
. You
should be able to confirm that with e.g.:
# curl -v 127.0.0.1
# systemctl status nginx
# systemctl status app
The VM contains other binaries to help interact with the system, and local documentation is available as man pages:
# cty --help
# man curiosity
Use ctrl-a x
to quit QEMU.
Note: when using the run-nixos-vm
script, a disk file called
nixos.qcow2
is created. It is re-used on the next call, and may create some
conflicts. Thus it is sometimes necessary to delete it before running the
script again.
An image suitable for Digital Ocean can also be built:
$ nix-build -A image
$ ls result/
nixos.qcow2.gz
Its weight is about 870MB.
The static documentation can be built by using the content
Nix attribute. It
is better to name its result symlink _site
instead of the default (result
)
be cause this is what cty serve
expects by default, as well as the
serve-doc.sh
helper script.
$ nix-build -A content --out-link _site
$ scripts/serve-doc.sh
Documentation pages use the Nginx
SSI feature, to embed
some HTML fragments that are generated by the cty serve
backend. This is also
used to embed a navigation bar (so that a logout or a login link is provided
depending on if the backend notices that the user is logged in or not).
To get a static navigation bar within the generated HTML pages instead of using
the SSI feature, it is possible to use the public
Nix attribute:
$ nix-build -A public --out-link _site
$ scripts/serve-doc.sh
These are raw notes about how the original smartcoop.sh
was deployed. I (Thu)
have used 4 scripts that come from my
nix-notes repository.
Those scripts require some environment variables for authentication, and some
configuration to point to the right services (DigitalOcean instead of AWS). I'm
using a .envrc
file, and a s3-config
file, that are not under version
control.
-
upload-image.sh
is used to upload the DigitalOcean image to Spaces (similar to S3). This gives us a URL from where the image can be downloaded for the next step. -
import-image.sh
is used to import the image as a "custom image" into DigitalOcean. This requires a URL from which DO will download the file. This was provided by the previous step. The command returns quickly, but the image will be shown as "Pending" in the DO web interface for quite a while.$ scripts/import-image.sh ID Name Type Distribution Slug Public Min Disk 110021225 curiosity custom Unknown OS false 0
The returned image ID is important for the next step.
-
create-droplet.sh
is used to create a Drople (i.e. a VM) at DO. The image ID from the previous step is hard-coded in the script. In addition, a public SSH key of mine, already known by DO, is specified in the script to be copied in the Droplet. After that, I note its IP address.I've confirmed I could SSH into it:
$ ssh [email protected]
I've also confirmed that the same commands shown above for the QEMU case also work.
It seems only 4.4GB are used on the 25GB disk.
-
deploy.sh
is used to deploy changes to the Droplet, without needing to rebuild an image or create a new Droplet. Note that I specifiedcty-2.hypered.systems
within the script instead of its IP address. See below.
This project was initially carried for Smart Coop, from August 2022 till
January 2023. The version done for Smart Coop is easily available in the
smartcoop.sh
branch. The corresponding domain expired in June 2023.
The work done done for Smart Coop is visible at cty-1.hypered.systems
.
(cty-2.hypered.systems
is used for the current version.)