Haze
Hazy with a chance of clouds.
haze is a tool that provides an easy way to set up Nextcloud test instances
with a choice of PHP version, database server, optional s3 or LDAP setup and
much more.
Quickstart
-
Grab a binary from the Codeberg releases and place it in your
$PATH -
Create a file
~/.config/haze/haze.tomlwith the following content:sources_root = "/path/to/nextcloud/sources" -
Start a basic Nextcloud instance:
haze start -
Navigate to the address that is provided in the output.
See the setup, configuration and usage for a more details.
Setup
Requirements
- Docker
haze is built around docker containers and manages containers using the docker socket. Using Podman with docker compatibility might also work, but is untested.
Installation
- Grab a binary from the
Codeberg releases and place it
in your
$PATH
Configuration
Create a file ~/.config/haze/haze.toml with the following options:
sources_root = "/path/to/nextcloud/sources"
See the configuration section for more options.
Test the Setup
Quick examples
-
Start a basic Nextcloud instance:
haze start -
Navigate to the address that is provided in the output.
Usage
Quick examples
-
Start a Nextcloud instance with
postgresql, ands3primary storage:haze start pgsql s3 -
Start a Nextcloud instance with
sqlite,php 8.3and ansmbexternal storage:haze start 8.3 smb -
Run specific units test against an
oracledatabasehaze test oracle apps/dav/tests/unit/Connector/Sabre
Managing instances
Starting an instance
haze start [--name <name>] [--detach] [database] [php-version] [services] [vX.Y.Z]
Where database is one of sqlite, mysql, mariadb, pgsql or oracle
with an optional version (e.g. pgsql:12), defaults to sqlite. And
php-version is one of 8.0, 8.1, 8.2, 8.3, 8.4 or 8.5, defaults to
the maximum version support by the current Nextcloud version.
You can specify a version number (e.g. v32.0.2) to use the sources from a
release instead of using the local sources.
Use --name <name> to give the instance a specific name instead of a randomly
generated one. For example:
Use --detach to give the instance
its own sources instead of sharing
sources_root with all other instances.
See the services documentation for a list of available services.
List running instances
haze
or
haze list
Stop an instance
haze [match] stop
Remove all unpinned running instances
haze clean
Pin an instance
haze [match] pin
Pinned instances will not be removed by haze clean.
Unpin an instance
haze [match] unpin
Run commands in a temporary instance
haze shell [database] [php-version] [services] [cmd]
This will create an instance, run the provided command, and cleanup the instance.
If no cmd is specified it will launch bash
Run tests in a new instance
haze test [database] [php-version] [services] [phpunit version] [path]
Where path is a file or folder to run PHPUnit in, relative to the sources
root.
This will create a fresh instance, run the PHPUnit tests, and clean up the created instance.
Interacting with running instances
The following commands run against the most recently started instance by default
and allow optionally providing a match to select a specific instance by its
name.
Open an instance in the browser
haze [match] open
Execute a command on an instance
haze [match] [service] [cmd]
If no cmd is specified it will launch bash
If a service name or db is provided, the command will be in the container of
the service or database.
If no cmd is specified it will launch bash
Execute an occ command on an instance
haze [match] occ [cmd]
Connect to the database on an instance
haze [match] db
Show the logs of an instance
haze [match] logs
Edit a file in an instance with the local $EDITOR
haze [match] edit <path>
Where <path> is the path of a file inside the container, for example
config/config.php.
Reload the PHP configuration of an instance
haze [match] reload
The PHP configuration can edit changed with haze edit /config/php.ini
Run a command with instance environment variables set
haze [match] env <cmd> [args]
Runs the provided command with NEXTCLOUD_URL, DATABASE_URL and REDIS_URL
environment variables set for the matched instance.
This is intended to run a local push daemon against an instance.
Git tools
Haze provides a couple of utilities to make working with many git repositories for apps easier.
Checkout a branch for all local apps
haze git checkout [branch]
Checks out the branch in all git repositories within the apps folder.
Defaults to the branch matching the current checked out server versions (e.g.
master or stable33).
master and main can be used interchangeably.
Pull remote changes for all local apps
haze git pull
Performs a pull in all git repositories within the apps folder.
Update the container images
haze update
Instances with their own sources
By default every instance shares the sources configured as sources_root, so
all instances always run the same code. An instance started with --detach gets
its own git worktree of sources_root instead, created in worktree_dir,
which lets you run several instances on different branches at the same time.
haze start --name my-fix --detach
The worktree is checked out with a detached head. Every app in one of the
app_directories that gets enabled during setup receives its own worktree as
well, other apps keep running from the shared app directory.
The worktrees are removed together with the instance, by haze stop or
haze clean.
Configuration
Configuration is loaded from ~/.config/haze/haze.toml.
The minimum required configuration needed to get started is just the
sources_root options.
The full list of supported options is:
sources_root
The local path of the Nextcloud sources. This options is required.
Type: string
app_directories
A list of additional app directory paths to look for apps into
Default []
Type: list of strings
work_dir
The location where haze keeps it’s temporary files and caches
Default: /tmp/haze
Type: string
worktree_dir
The location to store git worktrees when using instances with detached sources.
Default: <work_dir>/worktrees
Type: string
auto_setup
Options for automatically setting up the newly created Nextcloud instance.
Examples:
[auto_setup]
username = "foo"
password = "bar"
enable_apps = ["files_external"]
disable_apps = ["contacts"]
post_setup = [
"occ group:add test",
]
config = { "enforce_theme" = "dark" }
[auto_setup]
enabled = false
auto_setup.enabled
Whether to automatically setup Nextcloud inside a created instance.
Default: true
Type: boolean
auto_setup.username
The username to use for the admin account during auto setup.
Default: admin
Type: string
auto_setup.password
The password to use for the admin account during auto setup.
Default: admin
Type: string
auto_setup.enabled_apps
Extra apps to enable after auto setup
Default: []
Type: list of strings
auto_setup.disabled_apps
Apps to disabled after auto setup
Default: []
Type: list of strings
auto_setup.post_setup
Commands to execute after auto setup
Default: []
Type: list of strings
auto_setup.config
System configuration options to set before auto setup
Default: {}
Type: Object
volume
Additional files or directories to bind-mount into instances.
Any number of volume options can be configured
Examples:
[[volume]]
source = "/tmp/haze-shared"
target = "/shared"
create = true
[[volume]]
source = "/home/me/Downloads"
target = "/Downloads"
read_only = true
volume.source
The source path on the host
Type: string
volume.target
The target path inside the container
Type: string
volume.create
Create the source directory on the host if it doesn’t exist already.
Default: false
Type: boolean
volume.read_only
Whether to mount the file or directory as read only
Default: false
Type: boolean
preset
Configured presets that can be used when creating instances.
Any number of presets can be configured.
Example:
[[preset]]
name = "groupfolders"
apps = ["groupfolders"]
commands = [
"occ groupfolders:create gf",
"occ groupfolders:group 1 admin read write share delete"
]
preset.name
The name of the preset, this is used when creating instances to select the preset.
Type: string without whitespace
preset.apps
A list of apps to enable when the preset is used.
Default: []
Type: list of strings
preset.commands
A list of commands to run post-setup when the preset is used.
Default: []
Type: list of strings
proxy
Configuration for the haze proxy. Only required when using the proxy.
[proxy]
address = "haze.example.com"
listen = "/run/haze/haze.sock"
https = true
cert = "/path/to/haze.test.crt"
key = "/path/to/haze.test.key"
proxy.listen
The IP and port or Unix socket for the proxy to listen on.
Required when the proxy is enabled.
Type: string
Examples:
listen = "/run/haze/haze.sock"
listen = "127.0.0.1:8080"
proxy.address
The base domain the proxy is accessible on.
Required if the proxy is enabled.
Type: string
proxy.https
Whether to enable built-in HTTPS support for the proxy.
Default: false
Type: boolean
proxy.cert
The path of the PEM encoded certificate chain to use for HTTPS.
Required if HTTPS is enabled for the proxy.
Type: string.
proxy.cert
The path of the PEM encoded private key to use for HTTPS.
Required if HTTPS is enabled for the proxy.
Type: string.
Proxy
By default, instances can be accessed by their IP. In order to get more memorable URLs and allow supporting HTTPS. haze comes with a builtin reverse proxy to allow using a wildcard domain.
Setup
- Setup a DNS record for
*.haze.example.comandhaze.example.compointing to your development machine. - Set the
proxyconfiguration with your domain and desired listen endpoint. - Set up a service to run
haze proxyin the background as your own user. A SystemD user service is recommended (see haze.service for an example). - If you’re already running a reverse proxy, configure your reverse proxy of
choice to proxy
*.haze.example.comandhaze.example.comto the proxy’s listen endpoint. - (Optionally) setup HTTPS for the proxy.
Configuration
Add the following configuration to the haze.toml configuration file:
[proxy]
address = "haze.example.com" # the base domain for the proxy to use
listen = "127.0.0.1:8080" # the port+ip to listen on
# listen = "/var/run/haze/haze.sock" # or a unix socket path
Without a reverse proxy
If you have no other http(s) servers on your development machine, you can setup things without a reverse proxy.
Simply configure the proxy to listen on port 80 (or 443 when using HTTPS).
Binding to port 80 or443 as a regular user requires either giving the haze
binary the net_bind_service capability with
sudo setcap cap_net_bind_service=+ep $(which haze) (this will have to be done
every time your upgrade haze) or configure your system to allow unprivileged
users to bind on the low port numbers. Using
sysctl net.ipv4.ip_unprivileged_port_start=80 and writing
net.ipv4.ip_unprivileged_port_start=80
to /etc/sysctl.d/bind.conf to make it persistent across reboot.
With a reverse proxy
If you’re already have other http(s) services listening on your machine, you’ll probably want to setup a reverse proxy to allow them to all be served on your machine.
The setup for this will depend on your reverse proxy of the choice, the
following example configuration is for nginx.
upstream haze-handler {
server unix:/var/run/haze/haze.sock;
}
server {
listen 80;
server_name *.haze.example.com;
location / {
proxy_pass http://haze-handler;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Usage
When the proxy is configured, generated URLs for the instances will use a
subdomain of the configured domain, e.g. the rolling-bees instance will be
available at rolling-bees.haze.example.com. Additionally, haze.example.com
will automatically point to the last created instance.
Additionally, the proxy allows access to the service containers trough either
<instance id>-<service id>.haze.example.com for a specific instance, or
<service-id>.haze.example.com for the last created instance. For example
rolling-bees-mail.haze.example.com will give access to the smtp4dev web
interface of the rolling-bees instance.
DNS
Since the domain name used for the instance is dynamic, a wildcard DNS record is required.
With your domain’s DNS provider
If you own a domain you would like to use, you can create a wildcard domain
within the DNS settings of your DNS provider. For example creating a record an
A record for *.haze.example.com with a value of 127.0.0.1 and a similar
one for haze.example.com.
With a local dnsmasq
If you do not own a “real” domain for using with haze, you can setup dnsmasq
locally to achieve the same goal instead.
How to install and enable dnsmasq will depend on your Linux distribution of
choice and should be documented by it’s documentation.
Once setup, a configuration line like
address=/haze.local/172.0.0.1
should be enough to point haze.local and *.haze.local to your local host.
HTTPS
The proxy can be setup to enable using HTTPS to access the running instances. Besides the warm and fuzzy feeling of knowing that nobody can snoop on the traffic that is happening completely local inside your machine. Accessing the page over HTTPS is required for some JavaScript features (such as service workers), as they are only available in “secure contexts”.
Getting a wildcard certificate
Since the domain name used for the instance is dynamic, a wildcard certificate is required.
Let’s encrypt
Let’s encrypt allows getting trusted wildcard certificates for free if you can use DNS validation.
How to setup DNS validation will depend on the specifics of the DNS provider and ACME client. This lists some DNS providers and supported ACME clients.
Self signed
You can also create a self-signed wildcard certificate using a tool like
mkcert. This certificate will not be trusted by your browser and tools like
curl, but you can add manually add it to the trusted certificates on your
system, or bypass the certificates warning in the browser/curl every time.
# Generate local wildcard certificate
mkcert -cert-file <path-to-your-certificates>haze.example.com.crt -key-file <path-to-your-certificates>haze.example.com.key '*.haze.example.com'
Using the certificate
Without reverse proxy
The haze proxy can serve over HTTPS directly, to enable that add the following
to the [proxy] section of your haze.toml.
https = true
cert = "/path/to/haze.example.com.crt"
key = "/path/to/haze.example.com.key"
You might also want to change the port it’s listening on to 443.
With a reverse proxy
This depends on what reverse proxy you have setup. The following example is for
nginx.
upstream haze-handler {
server unix:/run/haze/haze.sock;
}
server {
listen 80;
listen 443 ssl;
http2 on;
server_name *.haze.example.com;
ssl_certificate <path-to-your-certificates>/haze.example.com.crt;
ssl_certificate_key <path-to-your-certificates>/haze.example.com.key;
location / {
proxy_pass http://haze-handler;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Services
The following service options are available:
s3: set up an S3 server and configure to Nextcloud to use it as primary storage.s3s: enable TLS for the S3 setup.s3mb: enable multi-bucket S3 setup.s3m: enable multi-instance S3 setup.
ldap: set up an LDAP server.saml: set up Authentik as a SAML IDP.oidc: set up Authentik as an OIDC IDP.scim: set up Authentik as a SCIM server.office: set up a Nextcloud Office server.onlyofficesetup an OnlyOffice document server.pushset up client push.smb: set up a samba server for external storage use.dav: set up a WebDAV server for external storage use.sftp: set up a SFTP server for external storage use.sftp-key: set up a SFTP server for external storage use with public key authentication.kaspersky: set up a Kaspersky scan engine server in HTTP mode. ( Requires manually setting up the image)kaspersky-icap: setup a Kaspersky scan engine server in ICAP mode.clamav: set up a local clam av scanner in executable mode.clamav-socket: set up a clam av scanner in socket mode.clamav-icap: set up a clam av scanner in ICAP mode.clamav-icap-tls: set up a clam av scanner in ICAP mode with TLS encryption.oc: start an ownCloud instance in the same network.imaginary: start an Imaginary service and configure it for preview generation.mail: start a smtp4dev server and configure it the mail server.webhookstart a webhook testerredis: start a separate container for Redis.redis-tls: connect to Redis over TLS.<path to app.tar.gz>: by specifying the path to an app package this package will be extracted into the apps. directory of the new instance (overwriting any existing app code). This can be used to quickly test a packaged app.- The name of any configured preset.
Federation
Multiple instances can reach each other by using their instance name as domain name to allow for testing federation between instances.
For example, if you have a rover-beavers and splendid-couch instance, you
can create a federated share from the rover-beavers instance to
http://admin@splendid-couch.
If the proxy is setup with HTTP, you can use HTTP between the instances by using the full proxy URLs.
For example sharing to admin@splendid-couch.haze.example.com.
Haze scripts
Haze scripts combine a set of instance options and a script to run in the instance.
Haze scripts are intended to way to create automated ways of running more complex tests are setting up more complex instances.
A script contains of 3 paths
- An optional shebang line setting
hazeas the interpreter, e.g.#! /usr/bin/env -S haze script - A shebang line setting the options for the instance creation as the
interpreter, e.g.
#! haze shell pgsql s3 - The rest that is ran as a script inside the created instance.
The first shebang is set, the script can be ran directly. Else it needs to be
run with haze script [path-to-script].
For example, the following script will create an instance with postgresql and
s3 primary storage. Then creates a new file, gets the file id, read the object
of the file from S3, validate that it contains the expected contents, and
cleanup the instance.
#! #! /usr/bin/env -S haze script
#! haze shell pgsql s3
echo 'test' | occ file:put '-' /admin/files/test.txt
FILE_ID=$(occ info:file /admin/files/test.txt | grep fileid: | grep -oE '[0-9]+')
OBJECT_CONTENTS=$(occ file:object:get urn:oid:$FILE_ID -)
if [ "$OBJECT_CONTENTS" == 'test' ]; then
echo "object contains expected contents"
else
echo "object does not contains expected contents!"
fi
Script modes
Scripts can be either shell or start scripts.
shell scripts will automatically remove the created instance once the script
is done, just like haze shell will. The intended use case for this is
performing some tests in the created instance without leaving state behind.
start scripts on the other hand will keep the instance, like haze start
will. The intended use case for this is creating more complex instances without
having to configure a dedicated preset.
The script mode is determined based on the shebang line. The mode set in a
script can be overridden by running the script with
haze script [shell|start] path-to-script.sh.
Using different interpreters
By default, the script contents are executed as either bash script, or nu
script based on the extension.
You can overwrite the interpreter used to execute the script by adding an extra
#! line to the script, for example:
#! /usr/bin/env -S haze script
#! haze shell pgsql s3
#! php -f
<?php
print("Hello");
Note that the interpreter used needs to already be available inside the haze container.
Xdebug
To use Xdebug running in a haze instance with your IDE for debugging, you need to tell you IDE how to properly map the path from the container to the host. The IDE debugger configuration usually looks like:
{
"label": "PHP: Debug server within docker",
"adapter": "Xdebug",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "<sources_root_configured_in_haze.toml>",
"/var/www/html/apps-extra": "<app_directories_configured_in_haze.toml>",
},
},
This would have to be adapted for detached instances.
Debugging all requests
By default, Xdebug is configured to only run for requests that containing the
trigger (e.g. from using the Xdebug browser extension, or adding
?XDEBUG_SESSION_START=1 to the URL).
To enable Xdebug for all requests:
- Un-comment the lines in
haze [cloud-id] edit /config/php.ini - Then run
haze reload [cloud-id]
FrankenPHP (experimental)
You can have Nextcloud run on FrankenPHP by starting the instance with the
franken-php option.
Caddy’s admin metrics are then accessible through
http://<cloud-id>-franken-php.haze.test. With ember you can run:
ember --addr http://<cloud-id>-franken-php.haze.test