1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-10-01 16:54:08 +02:00

Compare commits

..

No commits in common. "main" and "v2.4.0" have entirely different histories.

75 changed files with 723 additions and 1853 deletions

View file

@ -1,61 +0,0 @@
name: "Book - PR"
on:
pull_request:
types:
- opened
- reopened
- synchronize
paths:
- ".forgejo/workflows/book-pr.yaml"
- "book/**"
concurrency:
group: page-preview
cancel-in-progress: false
jobs:
createPreview:
runs-on: nix
env:
SITE_ORIGIN:
"https://${{ forge.event.repository.owner.username
}}.preview.codeberg.page"
SITE_BASEPATH:
"/${{ forge.event.repository.name }}@${{ forge.event.number }}/"
steps:
- uses: actions/checkout@v4
- uses: https://codeberg.org/icewind/attic-action@v1
with:
name: link
instance: https://cache.icewind.link
authToken: "${{ secrets.ATTIC_TOKEN }}"
- name: Build
run: |
# git-pages/action doesn't like symlinks
cp -r $(nix build .#haze-book --print-out-paths --no-link) dist
- name: "Deploy Site"
uses: https://code.forgejo.org/actions/git-pages@v2
with:
site: "${{ env.SITE_ORIGIN }}${{ env.SITE_BASEPATH }}" #
token: "${{ forge.token }}" #
source: "dist/" #
- name: "Find comment"
uses: "https://github.com/peter-evans/find-comment@v4" #
id: comment
with:
issue-number: ${{ forge.event.number }} #
body-includes: "<!-- preview-comment -->" #
- name: "Create or update comment"
uses: "https://github.com/peter-evans/create-or-update-comment@v5" #
with:
issue-number: ${{ forge.event.number }} #
comment-id: "${{ steps.comment.outputs.comment-id }}" #
edit-mode: "replace"
body: |-
# Pull request preview ready!
The Preview of commit ${{ forge.event.pull_request.head.sha }} has been created.
You can find it here: ${{ env.SITE_ORIGIN }}${{ env.SITE_BASEPATH }}
<!-- preview-comment -->

View file

@ -1,29 +0,0 @@
name: "Book"
on:
push:
branches:
- main
jobs:
preview:
runs-on: nix
env:
SITE_ORIGIN: "${{forge.event.repository.owner.username}}.codeberg.page"
SITE_BASEPATH: "/${{forge.event.repository.name}}/"
steps:
- uses: actions/checkout@v4
- uses: https://codeberg.org/icewind/attic-action@v1
with:
name: link
instance: https://cache.icewind.link
authToken: "${{ secrets.ATTIC_TOKEN }}"
- name: Build
run: |
# git-pages/action doesn't like symlinks
cp -r $(nix build .#haze-book --print-out-paths --no-link) dist
- uses: https://codeberg.org/git-pages/action@v2
with:
site: https://${{ env.SITE_ORIGIN }}${{ env.SITE_BASEPATH }}
token: ${{ forge.token }}
source: dist/

View file

@ -4,11 +4,13 @@ on:
push: push:
branches: ["main"] branches: ["main"]
paths: paths:
- "flake.*"
- "Cargo.toml" - "Cargo.toml"
- ".forgejo/workflows/docker.yaml" - ".forgejo/workflows/docker.yaml"
- "nix/image/**" - "nix/image/**"
permissions:
contents: read
jobs: jobs:
build-images: build-images:
runs-on: nix runs-on: nix

2
.gitignore vendored
View file

@ -2,5 +2,3 @@
.direnv .direnv
.env .env
result result
.vale/*
!.vale/config

View file

@ -1,13 +0,0 @@
StylesPath = .vale
MinAlertLevel = suggestion
Vocab = haze
[formats]
mdx = md
[*]
BasedOnStyles = Vale
[*.{md,mdx}]
BasedOnStyles = Vale
Microsoft.Contractions = NO

View file

@ -1,26 +0,0 @@
Nextcloud
ownCloud
appstore
Authentik
Caddy
cron
FrankenPHP
PHPUnit
mdBook
boolean
Codeberg
Kaspersky
Redis
Podman
Xdebug
occ
dnsmasq
notify_push
sharding
tokio
worktree
worktrees
config
stdin
stdout
direnv

View file

@ -1,51 +1,37 @@
## 2.4.2
- Support having a `#!` inside a haze script to specify the interpreter to use
- Add WebUI for accessing the database.
- Allow opening services with `haze open [service]`
## 2.4.1
- Show database location on start
- Direct TLS listening support for the proxy
- Set `SERVER_NAME` for FrankenPHP
- Expose Caddy admin endpoints when using FrankenPHP
- Use cron for background jobs
## 2.4.0 ## 2.4.0
- Show instance details in shell prompt - Show instance details in shell prompt
- Add option to start an instance with a detached worktree - Add option to start an instance with a detached worktree
- Add "haze scripts" that bundle a setup configuration and script to run in the - Add "haze scripts" that bundle a setup configuration and script to run in the
instance instance
- Add option to specify PHPUnit version when running tests - Allow specifying phpunit version when running tests
- Add SAML service using Authentik - Add SAML service using authentik
- Add OIDC service using Authentik - Add OIDC service using authentik
- Add SCIM service using Authentik - Add SCIM service using authentik
- Fix using a single word as instance name - Fix using a single word as instance name
- Fixed cleanup not removing some cache files - Fixed cleanup not removing some cache files
- Remove memory and CPU limits from containers - Remove memory and cpu limits from containers
- Fix max upload size not being set correctly - Fix max upload size not being set correctly
## 2.3.0 ## 2.3.0
- Add option to `exec` in service containers - Allow execing into service containers
- Add SFTP with key authentication service - Add sftp with key authentication service
- Fix MySQL 8 support - Fix mysql 8 support
## 2.2.2 ## 2.2.2
- parallelize `git pull` - parallelize `git pull`
- add webhook tester - add webhook tester
- automatically configure LDAP when enabled - automatically configure ldap when enabled
- improve compatibility with integration tests - improve compatibility with intergration tests
- add `php-imagick` module - add `php-imagick` module
## 2.1.1 ## 2.1.1
- Add basic [FrankenPHP](https://github.com/php/frankenphp) support - Add basic [frankenphp](https://github.com/php/frankenphp) support
- Allow running integration tests from (some) apps - Allow running integration tests from (some) apps
- Support federation with proxy and using 127.0.0.1 as proxy IP - Support federation with proxy and using 127.0.0.1 as proxy ip
- Fix office not working - Fix office not working
- Warn when using out-of-date images. - Warn when using out-of-date images.
@ -55,8 +41,8 @@
- Faster stopping of instances, by @provokateurin - Faster stopping of instances, by @provokateurin
- Support extra app directories - Support extra app directories
- Enable appstore - Enable appstore
- Allow setting configuration options pre-setup - Allow setting config options pre-setup
- Improved access to service containers - Improved access to service containers
- Add S3 with TLS options - Add S3 with TLS options
- Updated PHP 8.0 and 8.1 images - Updated php 8.0 and 8.1 images
- Add PHP 8.5 support - Add php 8.5 support

4
Cargo.lock generated
View file

@ -899,7 +899,7 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "haze" name = "haze"
version = "2.4.2" version = "2.4.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"atty", "atty",
@ -933,7 +933,6 @@ dependencies = [
"tar", "tar",
"termion", "termion",
"tokio", "tokio",
"tokio-rustls",
"tokio-stream", "tokio-stream",
"toml", "toml",
"tracing", "tracing",
@ -2017,7 +2016,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"log",
"once_cell", "once_cell",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "haze" name = "haze"
version = "2.4.2" version = "2.4.0"
edition = "2024" edition = "2024"
description = "Easy setup and management of Nextcloud test instances using docker" description = "Easy setup and management of Nextcloud test instances using docker"
repository = "https://codeberg.org/icewind/haze" repository = "https://codeberg.org/icewind/haze"
@ -44,7 +44,6 @@ tokio = { version = "1.53.1", features = [
"rt-multi-thread", "rt-multi-thread",
"signal" "signal"
] } ] }
tokio-rustls = "0.26"
tokio-stream = { version = "0.1.19", features = ["net"] } tokio-stream = { version = "0.1.19", features = ["net"] }
toml = "1.1.4" toml = "1.1.4"
tracing = "0.1.44" tracing = "0.1.44"

View file

@ -1,80 +0,0 @@
# Developing
## Setup
### Nix based development environment
The recommended way to setup the development environment is to use
[Lix](https://lix.systems/install/)/[Nix](https://nixos.org/download/).
Once installed, you can enter the development shell using `nix develop` or,
setup [direnv](https://direnv.net/) to automatically enter the development shell
when you enter the folder.
The development shell provides all necessary tooling to develop haze.
### Without Nix
The fooling tools need to be setup to develop without using Nix to manage the
development setup:
- [`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html) -
For building the program
- [`mdbook`](https://github.com/rust-lang/mdBook) - For building the
documentation
- [`prettier`](https://prettier.io/) - For formatting the non-rust files
- [`vale`](https://vale.sh/) - For checking documentation
## Building
```bash
cargo build
```
The binary can then be found at `target/debug/haze`.
## Formatting
### With Nix
```bash
nix fmt
```
### Without Nix
#### Rust code
```bash
cargo fmt
```
#### Documentation
```bash
prettier --write '**/*.md'
```
### Building documentation
```bash
mdbook serve
```
### Checking documentation
```bash
vale src/ book/src/ book/README.md *.md
```
### Run all checks
```bash
nix flake check
```
### Building docker images
```bash
nix run .#'"haze-image-php-8.4"'.copyToDockerDaemon
```

462
README.md
View file

@ -7,35 +7,473 @@ Easy setup and management of Nextcloud test instances using docker
## What ## What
`haze` provides an easy way to set up Nextcloud test instances with a choice of `haze` provides an easy way to set up Nextcloud test instances with a choice of
PHP version, database server, optional s3 or LDAP setup and more. php version, database server, optional s3 or ldap setup and more.
## Documentation ## Setup
Documentation for haze can be found in the ### Requirements
[book](https://icewind.codeberg.page/haze/)
## Quickstart - Docker
### Installation
- Grab a binary from the - Grab a binary from the
[Codeberg releases](https://codeberg.org/icewind/haze/releases) and place it [Codeberg releases](https://codeberg.org/icewind/haze/releases) and place it
in your `$PATH` in your `$PATH`
- Create a file `~/.config/haze/haze.toml` with the following content: ### Config
Create a file `~/.config/haze/haze.toml` with the following options:
```toml ```toml
sources_root = "/path/to/nextcloud/sources" sources_root = "/path/to/nextcloud/sources"
``` ```
- Start a basic Nextcloud instance: See the [configuration section](#configuration) for more options.
### Quick examples
- Start a Nextcloud instance with `postgresql`, and `s3` primary storage:
```bash ```bash
haze start haze start pgsql s3
``` ```
- Navigate to the address that is provided in the output. - Start a Nextcloud instance with `sqlite`, `php 8.3` and an `smb` external
storage:
See the [book](https://icewind.codeberg.page/haze/) for more details. ```bash
haze start 8.3 smb
```
## Developing - Run specific units test against an `oracle` database
```bash
haze test oracle apps/dav/tests/unit/Connector/Sabre
```
See [DEVELOPING.md](./DEVELOPING.md) for information on how to work on `haze`. ## Managing instances
#### Start an instance
```bash
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](#instances-with-their-own-sources) instead of sharing
`sources_root` with all other instances.
Additionally, you can use the following options when starting an instance:
- `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.
- `onlyoffice` setup an onlyoffice document server.
- `push` set up [client push](https://github.com/nextcloud/notify_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](https://github.com/icewind1991/kaspersky-docker))
- `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](https://github.com/rnwood/smtp4dev) server and
configure it the mail server.
- `webhook` start a
[webhook tester](https://github.com/tarampampam/webhook-tester)
- `redis`: 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.
#### Run tests in a new instance
```bash
haze test [database] [php-version] [phpunit version] [path]
```
Where `path` is a file or folder to run PHPUnit in, relative to the sources
root.
### List running instances
```bash
haze
```
or
```bash
haze list
```
#### Remove all running instances
```bash
haze clean
```
### 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.
```bash
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`.
## Controlling running instances
The following commands run against the most recently started instance and allow
optionally providing a `match` to select a specific instance by its name.
#### Open an instance
```bash
haze [match] open
```
#### Open the database of an instance
```bash
haze [match] db
```
#### Execute a command on an instance
```bash
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.
#### Create a new instance and run a command
```bash
haze [match] shell [cmd]
```
If no `cmd` is specified it will launch `bash`
#### Execute an occ command on an instance
```bash
haze [match] occ [cmd]
```
#### Connect to the database on an instance
```bash
haze [match] db
```
#### Show the logs of an instance
```bash
haze [match] logs
```
#### Stop an instance
```bash
haze [match] stop
```
#### Pin an instance
```bash
haze [match] pin
```
Pinned instances will not be removed by `haze clean`.
#### Unpin an instance
```bash
haze [match] unpin
```
#### Run a command with instance environment variables set
```bash
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](https://github.com/nextcloud/notify_push) against an instance.
#### Update the container images
```bash
haze update
```
#### Edit a file in an instance with the local $EDITOR
```bash
haze [match] edit <path>
```
#### Reload the php config of an instance
```bash
haze [match] reload
```
The php configuration can edit changed with `haze edit /config/php.ini`
#### Checkout a branch for all local apps
```bash
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
```bash
haze git pull
```
Performs a pull in all git repositories within the apps folder.
## Federation
Multiple instances can reach each other by using their instance name as domain
name to allow for testing federation between instances. Alternatively, you can
set up the haze proxy and the proxied domains to get https support between
instances.
## 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.
### Requirements
- A domain name you can set wildcard DNS records for
- A reverse proxy like Nginx or Apache
- (optionally) a wildcard ssl certificate (can be acquiring using letsencrypt
and dns verification)
### DNS Setup
- Set a DNS record for `*.haze.example.com` and `haze.example.com` pointing to
your development machine.
- Set the `proxy` configuration with your domain and desired listen endpoint.
- Set up a service to run `haze proxy` in the background as your own user. A
systemd user service is recommended (see [haze.service](./haze.service) for an
example).
- Configure your reverse proxy of choice to proxy `*.haze.example.com` and
`haze.example.com` to the proxy's listen endpoint
- (optional) acquire a wildcard ssl certificate for your domain and set your
reverse proxy to use it. This will be highly dependent on your DNS provider,
[this](https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438)
lists some DNS providers and supported ACME clients.
### Local Setup
- Setup `dnsmasq` to resolve `*.haze.test` to your development machine.
- Generate a wildcard ssl certificate for `*.haze.test` using `mkcert`:
```bash
# Generate local wildcard certificate
mkcert -cert-file <path-to-your-certificats>haze.test.crt -key-file <path-to-your-certificats>haze.test.key '*.haze.test'
```
- Set up a service to run `haze proxy` in the background as your own user. A
systemd user service is recommended (see [haze.service](./haze.service) for an
example).
- Setup a reverse proxy to proxy `*.haze.test` and `haze.test` to the
`haze proxy`'s socket. Example for Nginx:
```nginx
upstream haze-handler {
server unix:/run/haze/haze.sock;
}
server {
listen 80;
listen 443 ssl;
http2 on;
server_name *.haze.test;
ssl_certificate <path-to-your-certificats>/haze.test.crt;
ssl_certificate_key <path-to-your-certificats>/haze.test.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;
}
}
```
### 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 server 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.
## 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
1. An optional shebang line setting `haze` as the interpreter, e.g.
`#! /usr/bin/env -S haze script`
2. A shebang line setting the options for the instance creation as the
interpreter, e.g. `#! haze shell pgsql s3`
3. The rest that is ran as a script inside the created instance.
The first sheband 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.
```bash
#! #! /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 overriden by running the script with
`haze script [shell|start] path-to-script.sh`.
## Nushell scripts
By default, the script contents are executed as a `bash` script, you can instead
execute the script as a `nushell` script by using `.nu` as the file extention.
## Configuration
Configuration is loaded from `~/.config/haze/haze.toml` and has the following
options
```toml
sources_root = "/path/to/sources" # path of the nextcloud sources. required
app_directories = ["/path/to/sources/more_app"] # paths to additional app directories.
work_dir = "/path/to/temp/dir" # path to temporary directory. optional, defaults to "/tmp/haze"
worktree_dir = "/path/to/worktrees" # where to create the worktrees of detached instances. optional, defaults to "<work_dir>/worktrees"
[auto_setup] # optional
enabled = false # whether or not to automatically install nextcloud on `haze start`. enabled by default
username = "foo" # username for admin user during auto setup. optional, defaults to "admin"
password = "bar" # password for admin user during auto setup. optional, defaults to "admin"
enable_apps = ["files_external"] # apps to enable after setup, defaults to []
disable_apps = ["contacts"] # apps to disable after setup, defaults to []
post_setup = [# commands to execute after setup, defaults to []
"occ group:add test",
]
config = { "foo" = "bar" } # configuration options to set before install
[[volume]] # optional
source = "/tmp/haze-shared"
target = "/shared"
create = true
[[volume]]
source = "/home/me/Downloads"
target = "/Downloads"
read_only = true
[proxy] # optional
address = "haze.example.com" # base domain
https = true # Is the proxy behind a https terminating proxy
listen = "/run/haze/haze.sock" # either a unix socket path
#listen = "127.0.0.1:8080" # or a socket address
# presets allow for easy usage of commonly used setups
[[preset]]
name = "groupfolders" # name of the preset
apps = ["groupfolders"] # app to enable
commands = ["occ groupfolders:create gf", "occ groupfolders:group 1 admin read write share delete"] # commands to run post-setup
```

1
book/.gitignore vendored
View file

@ -1 +0,0 @@
book

View file

@ -1,9 +0,0 @@
## Build requirements
- [mdBook](https://github.com/rust-lang/mdBook)
## Development
```
mdbook serve
```

View file

@ -1,4 +0,0 @@
[book]
title = "Haze"
authors = ["Robin Appelman"]
language = "en"

View file

@ -1,30 +0,0 @@
# 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](https://codeberg.org/icewind/haze/releases) and place it
in your `$PATH`
- Create a file `~/.config/haze/haze.toml` with the following content:
```toml
sources_root = "/path/to/nextcloud/sources"
```
- Start a basic Nextcloud instance:
```bash
haze start
```
- Navigate to the address that is provided in the output.
See the [setup](./setup.html), [configuration](./configuration.html) and
[usage](./usage.html) for a more details.

View file

@ -1,16 +0,0 @@
# Summary
[Introduction](README.md)
- [Setup](setup.md)
- [Usage](usage.md)
- [Configuration](configuration.md)
- [Proxy](proxy/README.md)
- [DNS](proxy/dns.md)
- [HTTPS](proxy/https.md)
- [Services](services.md)
- [Database](database.md)
- [Federation](federation.md)
- [Haze scripts](scripts.md)
- [Xdebug](xdebug.md)
- [FrankenPHP (experimental)](frankenphp.md)

View file

@ -1,271 +0,0 @@
# 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:
```toml
[auto_setup]
username = "foo"
password = "bar"
enable_apps = ["files_external"]
disable_apps = ["contacts"]
post_setup = [
"occ group:add test",
]
config = { "enforce_theme" = "dark" }
```
```toml
[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:
```toml
[[volume]]
source = "/tmp/haze-shared"
target = "/shared"
create = true
```
```toml
[[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:
```toml
[[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.
```toml
[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:
```toml
listen = "/run/haze/haze.sock"
```
```toml
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.

View file

@ -1,33 +0,0 @@
# Database
There are 3 ways of accessing the database of a haze instance.
## CLI
```bash
haze [filter] db [query]
```
Opens the command line client for the database. If a query is specified, it will
be executed. If no query is specified, an interactive shell will be started.
Example
```
haze db 'select fileid, storage, path from oc_filecache'
```
## WebUI
A web UI (based on [DbGate](https://www.dbgate.io/)) is available with every
instance.
It can be accessed by using `haze open db`, or, if the proxy is setup at
`db.haze.example.com`.
## External tools
For usage with external tools, the database connection URL is printed when the
instance is created.
Details on how to use the connection URL is dependent on the external tool.

View file

@ -1,13 +0,0 @@
# 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`.

View file

@ -1,11 +0,0 @@
# 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:
```bash
ember --addr http://<cloud-id>-franken-php.haze.test
```

View file

@ -1,91 +0,0 @@
# 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](./dns.html) for `*.haze.example.com` and
`haze.example.com` pointing to your development machine.
- Set the `proxy` configuration with your domain and desired listen endpoint.
- Set up a service to run `haze proxy` in the background as your own user. A
SystemD user service is recommended (see
[haze.service](<[./haze.service](https://codeberg.org/icewind/haze/src/branch/main/haze.service)>)
for an example).
- If you're already running a reverse proxy, configure your reverse proxy of
choice to proxy `*.haze.example.com` and `haze.example.com` to the proxy's
listen endpoint.
- (Optionally) [setup HTTPS](./https.html) for the proxy.
### Configuration
Add the following configuration to the `haze.toml` configuration file:
```toml
[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`.
```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.

View file

@ -1,27 +0,0 @@
# 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.

View file

@ -1,78 +0,0 @@
# 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](https://community.letsencrypt.org/t/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation/86438)
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.
```bash
# 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`.
```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`.
```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;
}
}
```

View file

@ -1,72 +0,0 @@
# 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
1. An optional shebang line setting `haze` as the interpreter, e.g.
`#! /usr/bin/env -S haze script`
2. A shebang line setting the options for the instance creation as the
interpreter, e.g. `#! haze shell pgsql s3`
3. 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.
```bash
#! #! /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:
```bash
#! /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.

View file

@ -1,41 +0,0 @@
# 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.
- `onlyoffice` setup an OnlyOffice document server.
- `push` set up [client push](https://github.com/nextcloud/notify_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](https://github.com/icewind1991/kaspersky-docker))
- `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](https://github.com/rnwood/smtp4dev) server and
configure it the mail server.
- `webhook` start a
[webhook tester](https://github.com/tarampampam/webhook-tester)
- `redis`: 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.

View file

@ -1,36 +0,0 @@
# 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](https://codeberg.org/icewind/haze/releases) and place it
in your `$PATH`
## Configuration
Create a file `~/.config/haze/haze.toml` with the following options:
```toml
sources_root = "/path/to/nextcloud/sources"
```
See the [configuration section](./configuration.html) for more options.
## Test the Setup
### Quick examples
- Start a basic Nextcloud instance:
```bash
haze start
```
- Navigate to the address that is provided in the output.

View file

@ -1,230 +0,0 @@
# Usage
## Quick examples
- Start a Nextcloud instance with `postgresql`, and `s3` primary storage:
```bash
haze start pgsql s3
```
- Start a Nextcloud instance with `sqlite`, `php 8.3` and an `smb` external
storage:
```bash
haze start 8.3 smb
```
- Run specific units test against an `oracle` database
```bash
haze test oracle apps/dav/tests/unit/Connector/Sabre
```
## Managing instances
### Starting an instance
```bash
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](#instances-with-their-own-sources) instead of sharing
`sources_root` with all other instances.
See the [services documentation](./services.html) for a list of available
services.
### List running instances
```bash
haze
```
or
```bash
haze list
```
### Stop an instance
```bash
haze [match] stop
```
### Remove all unpinned running instances
```bash
haze clean
```
### Pin an instance
```bash
haze [match] pin
```
Pinned instances will not be removed by `haze clean`.
### Unpin an instance
```bash
haze [match] unpin
```
## Run commands in a temporary instance
```bash
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
```bash
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
```bash
haze [match] open
```
### Execute a command on an instance
```bash
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
```bash
haze [match] occ [cmd]
```
### Connect to the database on an instance
```bash
haze [match] db
```
### Show the logs of an instance
```bash
haze [match] logs
```
### Edit a file in an instance with the local $EDITOR
```bash
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
```bash
haze [match] reload
```
The PHP configuration can edit changed with `haze edit /config/php.ini`
### Run a command with instance environment variables set
```bash
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](https://github.com/nextcloud/notify_push) 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
```bash
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
```bash
haze git pull
```
Performs a pull in all git repositories within the apps folder.
## Update the container images
```bash
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.
```bash
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`.

View file

@ -1,36 +0,0 @@
# 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:
```json
{
"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]`
## Xdebug profile and trace files
When enabling Xdebug trace or profile mode, the generated files can be found in
`<work-dir>/<instance id>/xdebug`.

View file

@ -1,6 +1,5 @@
#! /usr/bin/env -S haze script #! /usr/bin/env -S haze script
#! haze shell pgsql s3 #! haze shell pgsql s3
#! /usr/bin/env nu
echo 'test' | occ file:put '-' /admin/files/test.txt echo 'test' | occ file:put '-' /admin/files/test.txt
FILE_ID=$(occ info:file /admin/files/test.txt | grep fileid: | grep -oE '[0-9]+') FILE_ID=$(occ info:file /admin/files/test.txt | grep fileid: | grep -oE '[0-9]+')

86
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1788465171, "lastModified": 1780099841,
"narHash": "sha256-Y1/TTVXjYXGF068IThQH9fPSZ0SIE74PABlUxnWTUH0=", "narHash": "sha256-EVZd2RsbpreRUDSi9rBwPY+ZxoyMaiEBbZxxhljbaS4=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "eb35abda9f232cc6610b1d1e3200d15c49b7ac54", "rev": "0532eb17955225173906d671fb36306bdeb1e2dc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -38,11 +38,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1790013821, "lastModified": 1781543995,
"narHash": "sha256-0OrPYAGfGF5BGPPtzEzeMNhNcvGyQBWRRfvcE0xY750=", "narHash": "sha256-wsSyxNWOSMofu4F5xGYGMrB1d8cepvBNhxhm9bGGmXg=",
"owner": "nix-community", "owner": "nix-community",
"repo": "flakelight", "repo": "flakelight",
"rev": "a57b0af9d0da4c5389ccf19e310daf0e5518312a", "rev": "fd1d557721e07b5a9d319442e4bcb5d286600011",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -60,11 +60,11 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1789480598, "lastModified": 1780231986,
"narHash": "sha256-G1jy1kz2dLJCkVjjY2PnCXEL1B2iqMlSfz01YFCBY+c=", "narHash": "sha256-OyafczPtzE0Xa2zl3j/KvV2+ZVYGhYQHt0MOVWtDXlY=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "c1e026caf1750f226a20dab66594dc1ef3a34ec4", "rev": "f5cbda29b945df03256bf63c22fa4cd5fa429e67",
"revCount": 77, "revCount": 72,
"type": "git", "type": "git",
"url": "https://codeberg.org/icewind/mill-scale.git" "url": "https://codeberg.org/icewind/mill-scale.git"
}, },
@ -80,11 +80,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1790258944, "lastModified": 1775487831,
"narHash": "sha256-3bwtAiifsfGXVLIopwhJ/RNIkDABcLg6DxnlbaiE2xc=", "narHash": "sha256-2lguQpLPQaxpQCJjXhmEEAfabwsAhkP29Z7fgLzHARA=",
"owner": "nlewo", "owner": "nlewo",
"repo": "nix2container", "repo": "nix2container",
"rev": "08d8889b6d2528acfce6e2616b0cd41983dedb02", "rev": "76be9608a7f4d6c985d28b0e7be903ae2547df3e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -95,11 +95,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1790218706, "lastModified": 1781216227,
"narHash": "sha256-6e4Na3z008XpdVyOXgfasXIn1aN+z8AXFG+jXdQSyuI=", "narHash": "sha256-9mUW6gNwoN2SWc/l0fW4svPNOulXLl8ijqKyeSOGgJE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c508844df6c28fa6dabc1b6af70f3ccbd65c5201", "rev": "a0374025a863d007d98e3297f6aa46cc3141c2f0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -110,11 +110,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1789370336, "lastModified": 1781268102,
"narHash": "sha256-6RSEDHIWQtesQKWSu5qRai8L2h4KgCgMEfJHstW99G4=", "narHash": "sha256-Zn5KTggEmUB3lXn/ccERNcBdddE6IaOFber9dWViWDg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c7def046b9a883d46974757852106483d741586f", "rev": "49a4bd0573c376468dd7996ddb6f9fa31d8c4d97",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -127,14 +127,15 @@
"phps": { "phps": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2",
"utils": "utils"
}, },
"locked": { "locked": {
"lastModified": 1790337121, "lastModified": 1781456983,
"narHash": "sha256-phqixEWMLjvdlUpo6uBCYbVYuGCk0FjiIoC0zPTCu3c=", "narHash": "sha256-z3SNuQpkSeIRTS6Y/Q5FgGAuGSY5+YJBXtqWPXw0f0k=",
"owner": "fossar", "owner": "fossar",
"repo": "nix-phps", "repo": "nix-phps",
"rev": "2a7ca28d4634890d2b07a63847cd6d26da9c2cb4", "rev": "6458735dece7e48f27d52ac68eee2d2cfe55b79a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -161,11 +162,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1789457514, "lastModified": 1780197589,
"narHash": "sha256-Aggle++fTyAifBy+QBPxjM+obO5iepKW/8MDxQtgGvI=", "narHash": "sha256-FVCr2Ij/jKf59a4LW481eeOF6rJRreOBrVgW/aUBTrw=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "89e99bf0778a8f2cd18c9360c3f19c1ee47fc739", "rev": "21632e942d89bf1cce4e5a63d7e58a215a0cbfcc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -173,6 +174,39 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -45,6 +45,14 @@
(final: prev: { (final: prev: {
inherit (phps.packages.${prev.system}) php81 php80; inherit (phps.packages.${prev.system}) php81 php80;
inherit (nix2container.packages.x86_64-linux) nix2container; inherit (nix2container.packages.x86_64-linux) nix2container;
blackfire = prev.blackfire.overrideAttrs (
oldAttrs: finalAttrs: {
src = final.fetchurl {
url = "https://packages.blackfire.io/debian/pool/any/main/b/blackfire/blackfire_${finalAttrs.version}_amd64.deb";
sha256 = "sha256-1fswfZEElLyXWqvNW76BpKTpBomK9cglJtitZgcpxhM=";
};
}
);
}) })
]; ];
@ -55,7 +63,6 @@
"haze-image-php-8.2" = pkgs: pkgs.haze-image-php-82; "haze-image-php-8.2" = pkgs: pkgs.haze-image-php-82;
"haze-image-php-8.1" = pkgs: pkgs.haze-image-php-81; "haze-image-php-8.1" = pkgs: pkgs.haze-image-php-81;
"haze-image-php-8.0" = pkgs: pkgs.haze-image-php-80; "haze-image-php-8.0" = pkgs: pkgs.haze-image-php-80;
haze-book = pkgs: pkgs.haze-book;
}; };
tools = pkgs: tools = pkgs:
@ -64,14 +71,8 @@
bacon bacon
skopeo skopeo
dive dive
mdbook
vale
]; ];
checks = {
vale = {vale, ...}: "${vale}/bin/vale src/ book/src/ book/README.md *.md";
};
homeModules = { homeModules = {
default = { default = {
pkgs, pkgs,

View file

@ -1,20 +0,0 @@
{
mdbook,
stdenv,
...
}:
stdenv.mkDerivation {
name = "haze-book";
src = ../book;
nativeBuildInputs = [mdbook];
buildPhase = ''
mdbook build
'';
installPhase = ''
mkdir -p $out
cp -r book/* $out/
'';
}

View file

@ -2,6 +2,7 @@
touch /var/log/nginx/access.log touch /var/log/nginx/access.log
touch /var/log/nginx/error.log touch /var/log/nginx/error.log
touch /var/log/cron/owncloud.log
mkdir /config mkdir /config
if not ("/config/php.ini" | path exists) { if not ("/config/php.ini" | path exists) {
@ -34,7 +35,6 @@ let dirs = [
let files = [ let files = [
["condition", "path"]; ["condition", "path"];
["/", "/var/www/html/build/integration/composer.lock"], ["/", "/var/www/html/build/integration/composer.lock"],
["/", "/var/log/cron/haze.log"]
] ]
$dirs | each { |dir| $dirs | each { |dir|
@ -88,7 +88,6 @@ if ("REDIS_TLS" in $env) {
cp /etc/supervisor/redis-tls.conf /etc/supervisor/enabled/ cp /etc/supervisor/redis-tls.conf /etc/supervisor/enabled/
} else { } else {
cp /etc/supervisor/redis-plain.conf /etc/supervisor/enabled/ cp /etc/supervisor/redis-plain.conf /etc/supervisor/enabled/
cp /etc/supervisor/redis-plain.conf /etc/supervisor/enabled/
} }
if ("BLACKFIRE_SERVER_ID" in $env) { if ("BLACKFIRE_SERVER_ID" in $env) {

View file

@ -3,4 +3,5 @@ runCommand "configs" {} ''
mkdir -p $out mkdir -p $out
cp -r ${./configs} $out/etc cp -r ${./configs} $out/etc
chmod -R +w $out/etc chmod -R +w $out/etc
mkdir $out/etc/supervisor/enabled/
'' ''

View file

@ -1,2 +0,0 @@
# m h dom mon dow user command
* * * * * haze php -f /var/www/html/cron.php -- -v >> /var/log/cron/haze.log 2>&1

View file

@ -0,0 +1,2 @@
# m h dom mon dow command
*/5 * * * * sudo -u haze php -f /var/www/html/cron.php >> /var/log/cron/haze.log 2>&1

View file

@ -1,2 +0,0 @@
[program:cron]
command = crond -x sch -f

View file

@ -1,5 +1,3 @@
[program:frankenphp] [program:frankenphp]
environment = SERVER_NAME=":80",CADDY_ADMIN=":2019"
command = /bin/frankenphp run command = /bin/frankenphp run
directory = /var/www/html directory = /var/www/html
user=haze

View file

@ -2,7 +2,7 @@
touch /var/log/nginx/access.log touch /var/log/nginx/access.log
touch /var/log/nginx/error.log touch /var/log/nginx/error.log
touch /var/log/cron/haze.log touch /var/log/cron/owncloud.log
if ("/var/www/html/config/config.php" | path exists) { if ("/var/www/html/config/config.php" | path exists) {
exit 0 exit 0

View file

@ -6,5 +6,4 @@ final: prev: {
haze-image-php-82 = final.callPackage ./image/haze.nix {php = final.php82;}; haze-image-php-82 = final.callPackage ./image/haze.nix {php = final.php82;};
haze-image-php-81 = final.callPackage ./image/haze.nix {php = final.php81;}; haze-image-php-81 = final.callPackage ./image/haze.nix {php = final.php81;};
haze-image-php-80 = final.callPackage ./image/haze.nix {php = final.php80;}; haze-image-php-80 = final.callPackage ./image/haze.nix {php = final.php80;};
haze-book = final.callPackage ./book.nix {};
} }

View file

@ -64,7 +64,6 @@ pub enum HazeArgs {
}, },
Open { Open {
filter: Option<String>, filter: Option<String>,
service: Option<Service>,
}, },
Fmt { Fmt {
path: String, path: String,
@ -318,20 +317,7 @@ impl HazeArgs {
.into_diagnostic()?, .into_diagnostic()?,
}) })
} }
HazeCommand::Open => { HazeCommand::Open => Ok(HazeArgs::Open { filter }),
let mut args = args.peekable();
let service = match args.peek() {
Some(arg) => Service::from_type(&[], arg.as_ref())
.into_iter()
.filter_map(|services| services.into_iter().next())
.next()
.inspect(|_| {
args.next();
}),
_ => None,
};
Ok(HazeArgs::Open { filter, service })
}
HazeCommand::Fmt => { HazeCommand::Fmt => {
let path = args let path = args
.next() .next()
@ -486,7 +472,6 @@ pub enum HazeCommand {
))] ))]
Logs, Logs,
/// Open an instance in the browser /// Open an instance in the browser
#[strum(props(Args = "[service] service to open, instead of the Nextcloud instance"))]
Open, Open,
/// Run code formatting from a new instance /// Run code formatting from a new instance
#[strum(props(Args = "[path] path to format"))] #[strum(props(Args = "[path] path to format"))]
@ -522,7 +507,7 @@ pub enum HazeCommand {
/// Edit a file in the instance with $EDITOR on the host /// Edit a file in the instance with $EDITOR on the host
#[strum(props(Args = "[path] file to edit"))] #[strum(props(Args = "[path] file to edit"))]
Edit, Edit,
/// Reload the PHP configuration in the instance /// Reload the php configuration in the instance
#[strum(props( #[strum(props(
Details = "note: you can overwrite <yellow>php.ini</yellow> settings with <literal>haze</literal> <arg>[filter]</arg> <literal>edit /config/php.ini</literal>" Details = "note: you can overwrite <yellow>php.ini</yellow> settings with <literal>haze</literal> <arg>[filter]</arg> <literal>edit /config/php.ini</literal>"
))] ))]

View file

@ -4,9 +4,9 @@ use crate::exec::{ExitCode, exec, exec_io, exec_tty};
use crate::git::{create_worktree, find_app_repo, init_submodules, remove_worktree}; use crate::git::{create_worktree, find_app_repo, init_submodules, remove_worktree};
use crate::mapping::{Mapping, for_config}; use crate::mapping::{Mapping, for_config};
use crate::php::PhpVersion; use crate::php::PhpVersion;
use crate::service::Service;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use crate::service::deduplicate_services; use crate::service::deduplicate_services;
use crate::service::{DbGate, Service};
use crate::sources::download_nc; use crate::sources::download_nc;
use bollard::Docker; use bollard::Docker;
use bollard::config::NetworkCreateRequest; use bollard::config::NetworkCreateRequest;
@ -347,8 +347,6 @@ fn test_option_parse() {
); );
} }
static DEFAULT_SERVICES: &[Service] = &[Service::DbGate(DbGate)];
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cloud { pub struct Cloud {
pub id: String, pub id: String,
@ -541,8 +539,6 @@ impl Cloud {
options options
.services .services
.iter() .iter()
.filter(|service| !service.hidden())
.chain(DEFAULT_SERVICES.iter())
.map(|service| service.spawn(docker, &id, &network, config, &options)), .map(|service| service.spawn(docker, &id, &network, config, &options)),
) )
.await?; .await?;
@ -812,8 +808,7 @@ impl Cloud {
&& match filter.as_ref() { && match filter.as_ref() {
Some(filter) => cloud_id.contains(filter), Some(filter) => cloud_id.contains(filter),
None => true, None => true,
} } {
{
let entry = containers_by_id.entry(cloud_id.to_string()).or_default(); let entry = containers_by_id.entry(cloud_id.to_string()).or_default();
if labels.get("haze-type").map(String::as_str) == Some("cloud") { if labels.get("haze-type").map(String::as_str) == Some("cloud") {
entry.0 = Some(container); entry.0 = Some(container);
@ -974,7 +969,7 @@ impl Cloud {
} }
pub fn services(&self) -> impl Iterator<Item = &Service> { pub fn services(&self) -> impl Iterator<Item = &Service> {
self.options.services.iter().chain(DEFAULT_SERVICES.iter()) self.options.services.iter()
} }
pub fn db(&self) -> &Database { pub fn db(&self) -> &Database {

View file

@ -201,14 +201,10 @@ pub struct ProxyConfig {
pub address: String, pub address: String,
#[serde(default)] #[serde(default)]
pub https: bool, pub https: bool,
#[serde(default)]
pub cert: Option<String>,
#[serde(default)]
pub key: Option<String>,
} }
impl ProxyConfig { impl ProxyConfig {
/// Get a public address for a service, either with direct IP or through the proxy /// Get a public address for a service, either with direct ip or through the proxy
pub fn addr(&self, id: &str, ip: IpAddr) -> String { pub fn addr(&self, id: &str, ip: IpAddr) -> String {
let clean_id = id.strip_prefix("haze-").unwrap_or(id); let clean_id = id.strip_prefix("haze-").unwrap_or(id);
match (&self.address, self.https) { match (&self.address, self.https) {

View file

@ -1,14 +1,13 @@
use crate::config::HazeConfig; use crate::exec::{exec, exec_tty, ExitCode};
use crate::exec::{ExitCode, exec, exec_tty};
use crate::image::pull_image; use crate::image::pull_image;
use bollard::Docker;
use bollard::config::ContainerCreateBody; use bollard::config::ContainerCreateBody;
use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Report, Result, WrapErr}; use miette::{IntoDiagnostic, Report, Result, WrapErr};
use std::io::{Stdout, stdout}; use std::io::{stdout, Stdout};
use std::net::{IpAddr, Ipv4Addr}; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use strum::{Display, EnumIter, EnumProperty, IntoStaticStr}; use strum::{Display, EnumIter, EnumProperty, IntoStaticStr};
@ -32,39 +31,6 @@ impl DatabaseFamily {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
self.into() self.into()
} }
pub fn db_gate_driver(&self) -> &'static str {
match self {
DatabaseFamily::Mysql => "mysql@dbgate-plugin-mysql",
DatabaseFamily::MariaDB => "mariadb@dbgate-plugin-mysql",
DatabaseFamily::Postgres => "postgres@dbgate-plugin-postgres",
DatabaseFamily::Oracle => "oracle@dbgate-plugin-oracle",
DatabaseFamily::Sqlite => "sqlite@dbgate-plugin-sqlite",
}
}
pub fn port(&self) -> u16 {
match self {
DatabaseFamily::Mysql | DatabaseFamily::MariaDB => 3306,
DatabaseFamily::Postgres => 5432,
DatabaseFamily::Oracle => 1521,
DatabaseFamily::Sqlite => 0,
}
}
pub fn username(&self) -> &'static str {
match self {
DatabaseFamily::Oracle => "system",
_ => "haze",
}
}
pub fn db(&self) -> &'static str {
match self {
DatabaseFamily::Oracle => "SYSTEM",
_ => "haze",
}
}
} }
#[derive(Clone, Debug, Eq, PartialEq, Default)] #[derive(Clone, Debug, Eq, PartialEq, Default)]
@ -448,37 +414,6 @@ impl Database {
} }
} }
pub async fn location(
&self,
docker: &Docker,
cloud_id: &str,
config: &HazeConfig,
) -> Result<String> {
let ip = match self.family() {
DatabaseFamily::Mysql
| DatabaseFamily::MariaDB
| DatabaseFamily::Postgres
| DatabaseFamily::Oracle => self
.ip(docker, cloud_id)
.await
.ok_or_else(|| Report::msg("Failed to get the IP of the database container"))?,
DatabaseFamily::Sqlite => IpAddr::V4(Ipv4Addr::LOCALHOST),
};
match self.family() {
DatabaseFamily::Mysql | DatabaseFamily::MariaDB => {
Ok(format!("mysql://haze:haze@{ip}/haze"))
}
DatabaseFamily::Postgres => Ok(format!("postgresql://haze:haze@{ip}/haze")),
DatabaseFamily::Oracle => Ok(format!("oracle://system:haze@{ip}:1521/XE")),
DatabaseFamily::Sqlite => Ok(config
.work_dir
.join(cloud_id)
.join("data/haze.db")
.to_string()),
}
}
pub async fn is_healthy(&self, docker: &Docker, cloud_id: &str, postfix: &str) -> Result<bool> { pub async fn is_healthy(&self, docker: &Docker, cloud_id: &str, postfix: &str) -> Result<bool> {
match self.family() { match self.family() {
DatabaseFamily::Sqlite => Ok(true), DatabaseFamily::Sqlite => Ok(true),

View file

@ -75,7 +75,7 @@ pub async fn exec_tty<S1: AsRef<str>, S2: Into<String>, Env: Into<String>>(
} }
}); });
// set stdout in raw mode so we can do TTY stuff // set stdout in raw mode so we can do tty stuff
let mut stdout = stdout.lock().into_raw_mode().into_diagnostic()?; let mut stdout = stdout.lock().into_raw_mode().into_diagnostic()?;
// pipe docker exec output into stdout // pipe docker exec output into stdout

View file

@ -151,15 +151,13 @@ fn subcommand_help(command: &dyn SubCommand) {
for service in ServiceType::iter() { for service in ServiceType::iter() {
let service: ServiceType = service; let service: ServiceType = service;
let service_str: &'static str = service.into(); let service_str: &'static str = service.into();
if let Some(doc) = service.get_documentation() {
println!( println!(
" {}{} {}", " {}{} {}",
service.blue(), service.blue(),
" ".repeat(max_service_len - service_str.len()), " ".repeat(max_service_len - service_str.len()),
doc, service.get_documentation().unwrap_or_default(),
); );
} }
}
println!(); println!();
println!("{}", "Options:".yellow().bold()); println!("{}", "Options:".yellow().bold());

View file

@ -1,6 +1,6 @@
use bollard::Docker;
use bollard::models::CreateImageInfo; use bollard::models::CreateImageInfo;
use bollard::query_parameters::CreateImageOptions; use bollard::query_parameters::CreateImageOptions;
use bollard::Docker;
use futures_util::StreamExt; use futures_util::StreamExt;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use miette::{IntoDiagnostic, Result, WrapErr}; use miette::{IntoDiagnostic, Result, WrapErr};

View file

@ -93,15 +93,8 @@ async fn main() -> Result<ExitCode> {
clear_networks(&docker, &retain).await?; clear_networks(&docker, &retain).await?;
for cache_dir in config for cache_dir in config.work_dir.read_dir().into_diagnostic()? {
.work_dir let cache_dir = cache_dir.into_diagnostic()?;
.read_dir()
.into_diagnostic()
.wrap_err_with(|| format!("Failed to read dir {}", config.work_dir))?
{
let cache_dir = cache_dir
.into_diagnostic()
.wrap_err_with(|| "Failed to unwrap cache_dir")?;
if let Some(id) = cache_dir.file_name().to_str() if let Some(id) = cache_dir.file_name().to_str()
&& id.starts_with("haze-") && id.starts_with("haze-")
&& !retain.iter().any(|cloud| cloud.id == id) && !retain.iter().any(|cloud| cloud.id == id)
@ -124,16 +117,12 @@ async fn main() -> Result<ExitCode> {
} }
} }
prune_worktrees(&config).wrap_err_with(|| "Failed to prune worktree")?; prune_worktrees(&config)?;
} }
HazeArgs::List { filter } => { HazeArgs::List { filter } => {
let list = Cloud::list(&docker, filter, &config).await?; let list = Cloud::list(&docker, filter, &config).await?;
for cloud in list { for cloud in list {
let mut services: Vec<_> = cloud let mut services: Vec<_> = cloud.services().map(Service::name).collect();
.services()
.filter(|service| !service.hidden())
.map(Service::name)
.collect();
services.push(cloud.db().name()); services.push(cloud.db().name());
let services = services.join(", "); let services = services.join(", ");
let pin = if cloud.is_pinned(&docker).await.unwrap_or(false) { let pin = if cloud.is_pinned(&docker).await.unwrap_or(false) {
@ -286,26 +275,11 @@ async fn main() -> Result<ExitCode> {
.await?; .await?;
} }
} }
HazeArgs::Open { filter, service } => { HazeArgs::Open { filter } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?; let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;
match service { match cloud.ip {
Some(service) => {
let Some(ip) = service.get_ips(&docker, &cloud.id).await?.next() else {
eprintln!(
"Service {} can't be opened as it has no associated IP",
service.name()
);
return Ok(ExitCode::FAILURE);
};
let addr = config
.proxy
.addr(&format!("{}-{}", cloud.id, service.name()), ip);
opener::open(addr).into_diagnostic()?;
}
None => match cloud.ip {
Some(_) => opener::open(cloud.address).into_diagnostic()?, Some(_) => opener::open(cloud.address).into_diagnostic()?,
None => eprintln!("{} is not running", cloud.id), None => eprintln!("{} is not running", cloud.id),
},
} }
} }
HazeArgs::Test { options, mut args } => { HazeArgs::Test { options, mut args } => {
@ -448,15 +422,21 @@ async fn main() -> Result<ExitCode> {
let ip = cloud let ip = cloud
.ip .ip
.ok_or_else(|| Report::msg(format!("{} is not running", cloud.id)))?; .ok_or_else(|| Report::msg(format!("{} is not running", cloud.id)))?;
match cloud.db().family() { let db_type = match cloud.db().family() {
DatabaseFamily::Sqlite => { DatabaseFamily::Sqlite => {
return Err(Report::msg("sqlite is not supported with `haze env`")); return Err(Report::msg("sqlite is not supported with `haze env`"));
} }
DatabaseFamily::Oracle => { DatabaseFamily::Oracle => {
return Err(Report::msg("oracle is not supported with `haze env`")); return Err(Report::msg("oracle is not supported with `haze env`"));
} }
_ => {} DatabaseFamily::Mysql | DatabaseFamily::MariaDB => "mysql",
} DatabaseFamily::Postgres => "postgresql",
};
let db_ip = cloud
.db()
.ip(&docker, &cloud.id)
.await
.ok_or_else(|| Report::msg(format!("{}-db is not running", cloud.id)))?;
let mut command = Command::new(command); let mut command = Command::new(command);
command command
@ -465,7 +445,7 @@ async fn main() -> Result<ExitCode> {
.env("NEXTCLOUD_URL", &cloud.address) .env("NEXTCLOUD_URL", &cloud.address)
.env( .env(
"DATABASE_URL", "DATABASE_URL",
cloud.db().location(&docker, &cloud.id, &config).await?, format!("{}://haze:haze@{}/haze", db_type, db_ip),
); );
if cloud.services().contains(&Service::RedisTls(RedisTls)) { if cloud.services().contains(&Service::RedisTls(RedisTls)) {
@ -539,7 +519,16 @@ async fn main() -> Result<ExitCode> {
&docker, &docker,
&cloud.id, &cloud.id,
"root", "root",
vec!["supervisorctl", "restart", "php-fpm"], vec!["pkill", "php-fpm"],
Vec::<String>::new(),
Some(stdout()),
)
.await?;
exec(
&docker,
&cloud.id,
"root",
vec!["sh", "-c", "php-fpm --fpm-config /etc/php-fpm.conf&"],
Vec::<String>::new(), Vec::<String>::new(),
Some(stdout()), Some(stdout()),
) )
@ -563,16 +552,6 @@ async fn setup(
let cloud = Cloud::create(docker, options, config).await?; let cloud = Cloud::create(docker, options, config).await?;
println!("{}", cloud.address); println!("{}", cloud.address);
let host = cloud.address.split_once("://").expect("no address?").1; let host = cloud.address.split_once("://").expect("no address?").1;
match cloud.db().location(docker, &cloud.id, config).await {
Ok(value) => {
println!("Database: {}", value);
}
Err(e) => {
println!("Failed to print DB location: {}", e);
}
}
if always_setup || config.auto_setup.enabled { if always_setup || config.auto_setup.enabled {
println!("Waiting for servers to start"); println!("Waiting for servers to start");
cloud.wait_for_start(docker).await?; cloud.wait_for_start(docker).await?;
@ -635,20 +614,6 @@ async fn setup(
) )
.await?; .await?;
} }
cloud
.occ(
docker,
vec![
"config:app:set",
"core",
"backgroundjobs_mode",
"--value",
"cron",
],
None,
Vec::<String>::default(),
)
.await?;
let domains = [ip_str.as_str(), "cloud", &cloud.id, host]; let domains = [ip_str.as_str(), "cloud", &cloud.id, host];
for (i, domain) in domains.iter().enumerate() { for (i, domain) in domains.iter().enumerate() {

View file

@ -1,6 +1,6 @@
use crate::cloud::Cloud; use crate::cloud::Cloud;
use bollard::Docker;
use bollard::config::NetworkCreateRequest; use bollard::config::NetworkCreateRequest;
use bollard::Docker;
use miette::{IntoDiagnostic, Result, WrapErr}; use miette::{IntoDiagnostic, Result, WrapErr};
pub async fn clear_networks(docker: &Docker, instances: &[Cloud]) -> Result<()> { pub async fn clear_networks(docker: &Docker, instances: &[Cloud]) -> Result<()> {
@ -12,8 +12,7 @@ pub async fn clear_networks(docker: &Docker, instances: &[Cloud]) -> Result<()>
for network in networks { for network in networks {
if let Some(name) = network.name.as_deref() if let Some(name) = network.name.as_deref()
&& let Some(id) = name.strip_prefix("haze-") && let Some(id) = name.strip_prefix("haze-")
&& !instances.iter().any(|cloud| cloud.id == id) && !instances.iter().any(|cloud| cloud.id == id) {
{
docker.remove_network(name).await.ok(); docker.remove_network(name).await.ok();
} }
} }

View file

@ -1,13 +1,13 @@
use crate::config::ProxyConfig; use crate::config::ProxyConfig;
use crate::database::Database; use crate::database::Database;
use crate::image::{ImageVersion, image_version, pull_image}; use crate::image::{image_version, pull_image, ImageVersion};
use crate::network::ensure_network_exists; use crate::network::ensure_network_exists;
use crate::service::Service; use crate::service::Service;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use bollard::Docker;
use bollard::config::{ContainerCreateBody, NetworkConnectRequest, NetworkingConfig}; use bollard::config::{ContainerCreateBody, NetworkConnectRequest, NetworkingConfig};
use bollard::models::{EndpointSettings, HostConfig}; use bollard::models::{EndpointSettings, HostConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use itertools::Itertools; use itertools::Itertools;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Report, Result, WrapErr}; use miette::{IntoDiagnostic, Report, Result, WrapErr};
@ -71,7 +71,7 @@ impl PhpVersion {
PhpVersion::Php82 => "8.2", PhpVersion::Php82 => "8.2",
PhpVersion::Php83 => "8.3", PhpVersion::Php83 => "8.3",
PhpVersion::Php84 => "8.4", PhpVersion::Php84 => "8.4",
PhpVersion::Php85 => "8.5", PhpVersion::Php85 => "8.4",
} }
} }
@ -127,8 +127,7 @@ impl PhpVersion {
let image_version = image_version(docker, self.image()).await; let image_version = image_version(docker, self.image()).await;
let haze_version = ImageVersion::from_str(env!("CARGO_PKG_VERSION")); let haze_version = ImageVersion::from_str(env!("CARGO_PKG_VERSION"));
if let (Some(image_version), Ok(haze_version)) = (image_version, haze_version) if let (Some(image_version), Ok(haze_version)) = (image_version, haze_version)
&& image_version < haze_version && image_version < haze_version {
{
eprintln!( eprintln!(
"{}: image version is out of date, run {} to update.", "{}: image version is out of date, run {} to update.",
"Warning".red(), "Warning".red(),

View file

@ -1,8 +1,8 @@
use crate::Result;
use crate::service::{ServiceTrait, ServiceType}; use crate::service::{ServiceTrait, ServiceType};
use crate::Result;
use crate::{Cloud, HazeConfig}; use crate::{Cloud, HazeConfig};
use axum::http::HeaderValue;
use axum::http::header::HOST; use axum::http::header::HOST;
use axum::http::HeaderValue;
use axum::{ use axum::{
body::Body, body::Body,
extract::Request, extract::Request,
@ -10,13 +10,13 @@ use axum::{
}; };
use bollard::Docker; use bollard::Docker;
use futures_util::StreamExt; use futures_util::StreamExt;
use hyper::StatusCode;
use hyper::body::Incoming; use hyper::body::Incoming;
use hyper::server::conn::http1; use hyper::server::conn::http1;
use hyper::service::service_fn; use hyper::service::service_fn;
use hyper::StatusCode;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use miette::{IntoDiagnostic, miette}; use miette::{miette, IntoDiagnostic};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::Infallible; use std::convert::Infallible;
use std::fs::{create_dir_all, set_permissions}; use std::fs::{create_dir_all, set_permissions};
@ -32,10 +32,6 @@ use tokio::net::UnixListener;
use tokio::signal::ctrl_c; use tokio::signal::ctrl_c;
use tokio::spawn; use tokio::spawn;
use tokio::time::sleep; use tokio::time::sleep;
use tokio_rustls::TlsAcceptor;
use tokio_rustls::rustls::ServerConfig;
use tokio_rustls::rustls::pki_types::pem::PemObject;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_stream::wrappers::{TcpListenerStream, UnixListenerStream}; use tokio_stream::wrappers::{TcpListenerStream, UnixListenerStream};
use tracing::{debug, error, info}; use tracing::{debug, error, info};
@ -151,34 +147,9 @@ pub async fn proxy(docker: Docker, config: HazeConfig) -> Result<()> {
} }
let listen = config.proxy.listen.clone(); let listen = config.proxy.listen.clone();
let acceptor = match (&config.proxy.cert, &config.proxy.key) {
(None, None) => None,
(Some(_), None) => return Err(miette!("`cert` is set without `key`")),
(None, Some(_)) => return Err(miette!("`key` is set without `cert`")),
(Some(cert), Some(key)) => Some(tls_acceptor(cert, key)?),
};
let base_address = config.proxy.address.clone(); let base_address = config.proxy.address.clone();
let instances = ActiveInstances::new(docker, config); let instances = ActiveInstances::new(docker, config);
serve(instances, listen, base_address, acceptor).await serve(instances, listen, base_address).await
}
/// Build a TLS acceptor from a PEM encoded certificate chain and private key on disk
fn tls_acceptor(cert: &str, key: &str) -> Result<TlsAcceptor> {
let certs = CertificateDer::pem_file_iter(cert)
.map_err(|e| miette!("failed to load certificate from {cert}: {e}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| miette!("failed to load certificate from {cert}: {e}"))?;
let key = PrivateKeyDer::from_pem_file(key)
.map_err(|e| miette!("failed to load private key from {key}: {e}"))?;
let mut server_config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.into_diagnostic()?;
server_config.alpn_protocols = vec![b"http/1.1".to_vec()];
Ok(TlsAcceptor::from(Arc::new(server_config)))
} }
#[derive(Clone)] #[derive(Clone)]
@ -188,12 +159,7 @@ struct AppState {
proxy_client: Arc<Client>, proxy_client: Arc<Client>,
} }
async fn serve( async fn serve(instances: ActiveInstances, listen: String, base_address: String) -> Result<()> {
instances: ActiveInstances,
listen: String,
base_address: String,
acceptor: Option<TlsAcceptor>,
) -> Result<()> {
let instances = Arc::new(instances); let instances = Arc::new(instances);
let base_address = Arc::new(base_address); let base_address = Arc::new(base_address);
let last_instances = instances.clone(); let last_instances = instances.clone();
@ -222,13 +188,12 @@ async fn serve(
if !listen.starts_with('/') { if !listen.starts_with('/') {
let addr: SocketAddr = listen.parse().into_diagnostic()?; let addr: SocketAddr = listen.parse().into_diagnostic()?;
let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
let scheme = if acceptor.is_some() { "https" } else { "http" }; println!("listening on {}", listener.local_addr().unwrap());
println!("Listening on {scheme}://{}", listener.local_addr().unwrap());
let mut connections = pin!(TcpListenerStream::new(listener).take_until(cancel)); let mut connections = pin!(TcpListenerStream::new(listener).take_until(cancel));
while let Some(stream) = connections.next().await { while let Some(stream) = connections.next().await {
match stream { match stream {
Ok(stream) => handle_connection(state.clone(), stream, acceptor.clone()).await, Ok(stream) => handle_connection(state.clone(), stream),
Err(error) => { Err(error) => {
error!(%error, "connection failed"); error!(%error, "connection failed");
} }
@ -237,8 +202,7 @@ async fn serve(
} else { } else {
let listen: PathBuf = listen.into(); let listen: PathBuf = listen.into();
if let Some(parent) = listen.parent() if let Some(parent) = listen.parent()
&& !parent.exists() && !parent.exists() {
{
create_dir_all(parent).into_diagnostic()?; create_dir_all(parent).into_diagnostic()?;
set_permissions(parent, PermissionsExt::from_mode(0o755)).into_diagnostic()?; set_permissions(parent, PermissionsExt::from_mode(0o755)).into_diagnostic()?;
} }
@ -252,7 +216,7 @@ async fn serve(
while let Some(stream) = connections.next().await { while let Some(stream) = connections.next().await {
match stream { match stream {
Ok(stream) => handle_connection(state.clone(), stream, None).await, Ok(stream) => handle_connection(state.clone(), stream),
Err(error) => { Err(error) => {
error!(%error, "connection failed"); error!(%error, "connection failed");
} }
@ -263,22 +227,7 @@ async fn serve(
Ok(()) Ok(())
} }
async fn handle_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>( fn handle_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
state: AppState,
stream: I,
acceptor: Option<TlsAcceptor>,
) {
// Spawn a tokio task to serve multiple connections concurrently
match acceptor {
Some(acceptor) => match acceptor.accept(stream).await {
Ok(stream) => serve_connection(state, stream).await,
Err(error) => error!(%error, "tls handshake failed"),
},
None => serve_connection(state, stream).await,
}
}
async fn serve_connection<I: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
state: AppState, state: AppState,
stream: I, stream: I,
) { ) {
@ -338,7 +287,7 @@ async fn handler(state: AppState, mut req: Request<Incoming>) -> Result<Response
return Ok(hyper::Response::builder() return Ok(hyper::Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
.body(e.into()) .body(e.into())
.unwrap()); .unwrap())
} }
}; };

View file

@ -1,4 +1,4 @@
use std::{fs::read_to_string, iter::once}; use std::fs::read_to_string;
use bollard::Docker; use bollard::Docker;
use camino::Utf8PathBuf; use camino::Utf8PathBuf;
@ -36,17 +36,15 @@ pub async fn run_script(
let contents = read_to_string(&path) let contents = read_to_string(&path)
.into_diagnostic() .into_diagnostic()
.wrap_err_with(|| format!("Failed to read script file {path}"))?; .wrap_err_with(|| format!("Failed to read script file {path}"))?;
let script = parse_script(&contents, config) let (mut options, mode) = parse_script(&contents, config)
.wrap_err_with(|| format!("Failed to parse script file {path}"))?; .wrap_err_with(|| format!("Failed to parse script file {path}"))?;
let mode = mode_override.unwrap_or(mode);
let mode = mode_override.unwrap_or(script.mode);
let mut options = script.options;
let target_path = Utf8PathBuf::from(format!("/{}", path.file_name().unwrap())); let target_path = Utf8PathBuf::from(format!("/{}", path.file_name().unwrap()));
let shell = match (script.shell, path.extension()) { let shell = if path.extension() == Some("nu") {
(Some(shell), _) => shell, "nu"
(None, Some("nu")) => vec!["nu".into()], } else {
_ => vec!["bash".into()], "bash"
}; };
options.mappings.push( options.mappings.push(
@ -63,10 +61,7 @@ pub async fn run_script(
cloud cloud
.exec( .exec(
docker, docker,
shell vec![shell.to_string(), target_path.into_string()],
.into_iter()
.chain(once(target_path.into_string()))
.collect(),
true, true,
get_forward_env(), get_forward_env(),
) )
@ -79,22 +74,14 @@ pub async fn run_script(
Ok(()) Ok(())
} }
#[derive(Debug)] fn parse_script(script: &str, config: &HazeConfig) -> Result<(CloudOptions, ScriptMode)> {
struct Script { let (options, mode) = script
options: CloudOptions,
mode: ScriptMode,
shell: Option<Vec<String>>,
}
fn parse_script(script: &str, config: &HazeConfig) -> Result<Script> {
let (options, mode, line_num) = script
.lines() .lines()
.enumerate() .find_map(|line| line.strip_prefix("#! haze "))
.find_map(|(i, line)| Some((i, line.strip_prefix("#! haze ")?)))
.and_then( .and_then(
|(i, line)| match (line.strip_prefix("shell"), line.strip_prefix("start")) { |line| match (line.strip_prefix("shell"), line.strip_prefix("start")) {
(Some(shell_options), None) => Some((shell_options.trim(), ScriptMode::Shell, i)), (Some(shell_options), None) => Some((shell_options.trim(), ScriptMode::Shell)),
(None, Some(script_options)) => Some((script_options.trim(), ScriptMode::Start, i)), (None, Some(script_options)) => Some((script_options.trim(), ScriptMode::Start)),
_ => None, _ => None,
}, },
) )
@ -103,19 +90,5 @@ fn parse_script(script: &str, config: &HazeConfig) -> Result<Script> {
let options = let options =
CloudOptions::parse(config, &mut options).wrap_err("Failed to parse shell options")?; CloudOptions::parse(config, &mut options).wrap_err("Failed to parse shell options")?;
Ok((options, mode))
let first_content_line = script.lines().nth(line_num + 1).unwrap_or_default();
let shell = first_content_line.strip_prefix("#!").map(|bang| {
bang.split(" ")
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
});
Ok(Script {
options,
mode,
shell,
})
} }

View file

@ -1,7 +1,6 @@
mod authentik; mod authentik;
mod clam; mod clam;
mod dav; mod dav;
mod dbgate;
mod imaginary; mod imaginary;
mod kaspersky; mod kaspersky;
mod ldap; mod ldap;
@ -23,7 +22,6 @@ use crate::config::{HazeConfig, Preset, ProxyConfig};
pub use crate::service::authentik::{Authentik, AuthentikOidc, AuthentikSaml, AuthentikScim}; pub use crate::service::authentik::{Authentik, AuthentikOidc, AuthentikSaml, AuthentikScim};
pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket}; pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket};
use crate::service::dav::Dav; use crate::service::dav::Dav;
pub use crate::service::dbgate::DbGate;
use crate::service::imaginary::Imaginary; use crate::service::imaginary::Imaginary;
use crate::service::kaspersky::{Kaspersky, KasperskyIcap}; use crate::service::kaspersky::{Kaspersky, KasperskyIcap};
pub use crate::service::ldap::{Ldap, LdapAdmin}; pub use crate::service::ldap::{Ldap, LdapAdmin};
@ -61,10 +59,6 @@ pub trait ServiceTrait {
&[] &[]
} }
fn hidden(&self) -> bool {
false
}
async fn spawn( async fn spawn(
&self, &self,
_docker: &Docker, _docker: &Docker,
@ -212,7 +206,7 @@ pub trait ServiceTrait {
"bash" "bash"
} }
/// Allow services to delay the completion of the `start` command until they are fully setup /// Allow services to delay the completion of the `start` command untill they are fully setup
/// ///
/// This is not for services which are critical for the running of the instance (use `is_healthy` for that) /// This is not for services which are critical for the running of the instance (use `is_healthy` for that)
/// But instead for cases where the instance is mostly usable, but might have some missing features /// But instead for cases where the instance is mostly usable, but might have some missing features
@ -242,14 +236,6 @@ impl ServiceTrait for FrankenPhp {
"franken-php" "franken-php"
} }
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(cloud_id.to_string())
}
fn proxy_port(&self) -> u16 {
2019
}
fn env(&self) -> &[&str] { fn env(&self) -> &[&str] {
&["FRANKENPHP=1"] &["FRANKENPHP=1"]
} }
@ -270,18 +256,18 @@ pub enum ServiceType {
S3mb, S3mb,
/// Azure Primary storage and external storage /// Azure Primary storage and external storage
Azure, Azure,
/// LDAP user backend /// Ldap user backend
Ldap, Ldap,
/// LDAP admin interface /// Ldap admin interface
LdapAdmin, LdapAdmin,
/// OnlyOffice /// OnlyOffice
#[strum(serialize = "onlyoffice", serialize = "only-office")] #[strum(serialize = "onlyoffice", serialize = "only-office")]
OnlyOffice, OnlyOffice,
/// LibreOffice online /// Libre office online
Office, Office,
/// notify_push /// notify_push
Push, Push,
/// SMB external storage /// Smb external storage
Smb, Smb,
/// Database sharding /// Database sharding
#[strum(serialize = "sharding", serialize = "sharded")] #[strum(serialize = "sharding", serialize = "sharded")]
@ -299,17 +285,17 @@ pub enum ServiceType {
SingleShard, SingleShard,
/// WebDav external storage /// WebDav external storage
Dav, Dav,
/// SFTP external storage /// Sftp external storage
Sftp, Sftp,
/// SFTP external storage with public key authentication /// Sftp external storage with public key authentication
SftpKey, SftpKey,
/// ownCloud instance for migration /// ownCloud instance for migration
Oc, Oc,
/// Imaginary for preview generation /// Imaginary for preview generation
Imaginary, Imaginary,
/// Kaspersky antivirus in HTTP mode /// Kaspersky antivirus in http mode
Kaspersky, Kaspersky,
/// Kaspersky antivirus in ICAP mode /// Kaspersky antivirus in icap mode
KasperskyIcap, KasperskyIcap,
/// Kaspersky antivirus in local binary /// Kaspersky antivirus in local binary
#[strum(serialize = "clamav", serialize = "clam")] #[strum(serialize = "clamav", serialize = "clam")]
@ -320,23 +306,23 @@ pub enum ServiceType {
/// Kaspersky antivirus in local socket mode /// Kaspersky antivirus in local socket mode
#[strum(serialize = "clamav-socket", serialize = "clam-socket")] #[strum(serialize = "clamav-socket", serialize = "clam-socket")]
ClamAvSocket, ClamAvSocket,
/// Kaspersky antivirus in ICAP mode /// Kaspersky antivirus in icap mode
#[strum(serialize = "clamav-icap", serialize = "clam-icap")] #[strum(serialize = "clamav-icap", serialize = "clam-icap")]
ClamAvIcap, ClamAvIcap,
/// Kaspersky antivirus in ICAP mode with TLS /// Kaspersky antivirus in icap mode with TLS
#[strum(serialize = "clamav-icap-tls", serialize = "clam-icap-tls")] #[strum(serialize = "clamav-icap-tls", serialize = "clam-icap-tls")]
ClamAvIcapTls, ClamAvIcapTls,
/// Mail server /// Mail server
Mail, Mail,
/// External Redis instance /// External redis instance
Redis, Redis,
/// External Redis instance with TLS /// External redis instance with TLS
RedisTls, RedisTls,
/// Use FrankenPHP instead of PHP-FPM /// Use FrankenPHP instead of PHP-FPM
FrankenPhp, FrankenPhp,
/// Webhook test listener /// Webhook test listener
Webhook, Webhook,
/// Enable DB partitioning for MariaDB /// Enable DB partitioning for mariadb
Partitioning, Partitioning,
/// Authentik identity provider /// Authentik identity provider
Authentik, Authentik,
@ -346,8 +332,6 @@ pub enum ServiceType {
Oidc, Oidc,
/// Configure Authentik as a SCIM server for Nextcloud /// Configure Authentik as a SCIM server for Nextcloud
Scim, Scim,
#[strum(serialize = "db", serialize = "db-gate")]
DbGate,
} }
#[enum_dispatch] #[enum_dispatch]
@ -385,7 +369,6 @@ pub enum Service {
AuthentikSaml(AuthentikSaml), AuthentikSaml(AuthentikSaml),
AuthentikOidc(AuthentikOidc), AuthentikOidc(AuthentikOidc),
AuthentikScim(AuthentikScim), AuthentikScim(AuthentikScim),
DbGate(DbGate),
Preset(PresetService), Preset(PresetService),
} }
@ -445,7 +428,6 @@ impl Service {
Service::Authentik(Authentik), Service::Authentik(Authentik),
Service::AuthentikScim(AuthentikScim), Service::AuthentikScim(AuthentikScim),
]), ]),
ServiceType::DbGate => Some(vec![Service::DbGate(DbGate)]),
} }
} else { } else {
presets presets
@ -475,7 +457,7 @@ impl Service {
} }
// Remove duplicate services. // Remove duplicate services.
// Useful for Authentik as it is needed by SAML and OIDC. // Useful for Authentik as it is needed by saml and oidc.
pub fn deduplicate_services(services: impl IntoIterator<Item = Service>) -> Vec<Service> { pub fn deduplicate_services(services: impl IntoIterator<Item = Service>) -> Vec<Service> {
let mut collected = Vec::new(); let mut collected = Vec::new();
for service in services { for service in services {

View file

@ -122,7 +122,7 @@ async fn spawn_authentik(
if !config.proxy.address.is_empty() { if !config.proxy.address.is_empty() {
let url = config.proxy.addr_with_port( let url = config.proxy.addr_with_port(
&container_name(cloud_id, "server"), &container_name(cloud_id, "server"),
IpAddr::V4(Ipv4Addr::LOCALHOST), // IP argument is unused when a proxy is configured IpAddr::V4(Ipv4Addr::LOCALHOST), // ip arg is unused when a proxy is configured
AUTHENTIK_PORT, AUTHENTIK_PORT,
); );
env.push(format!("AUTHENTIK_WEB__BASE_URL={url}")); env.push(format!("AUTHENTIK_WEB__BASE_URL={url}"));

View file

@ -76,7 +76,7 @@ impl ServiceTrait for AuthentikSaml {
"--silent".into(), "--silent".into(),
"1".into(), "1".into(),
"--general-idp0_display_name=Authentik SAML".into(), "--general-idp0_display_name=Authentik SAML".into(),
"--general-uid_mapping=http://haze.test/nextcloud/uid".to_string(), format!("--general-uid_mapping=http://haze.test/nextcloud/uid"),
format!("--idp-entityId={authentik_url}/application/saml/nextcloud-saml/metadata/"), format!("--idp-entityId={authentik_url}/application/saml/nextcloud-saml/metadata/"),
format!("--idp-singleSignOnService.url={authentik_url}/application/saml/nextcloud-saml/"), format!("--idp-singleSignOnService.url={authentik_url}/application/saml/nextcloud-saml/"),
format!("--idp-singleLogoutService.url={authentik_url}/application/saml/nextcloud-saml/"), format!("--idp-singleLogoutService.url={authentik_url}/application/saml/nextcloud-saml/"),

View file

@ -1,12 +1,12 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, WrapErr}; use miette::{IntoDiagnostic, WrapErr};
use tokio::fs::write; use tokio::fs::write;
@ -92,9 +92,7 @@ impl ServiceTrait for ClamIcap {
split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap"), split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap"),
split_cmnd("occ config:app:set files_antivirus av_port --value=1344"), split_cmnd("occ config:app:set files_antivirus av_port --value=1344"),
split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"),
split_cmnd( split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"),
"occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found",
),
]) ])
} }
} }
@ -198,9 +196,7 @@ impl ServiceTrait for ClamIcapTls {
split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap-tls"), split_cmnd("occ config:app:set files_antivirus av_host --value=clamav-icap-tls"),
split_cmnd("occ config:app:set files_antivirus av_port --value=1345"), split_cmnd("occ config:app:set files_antivirus av_port --value=1345"),
split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"), split_cmnd("occ config:app:set files_antivirus av_icap_request_service --value=avscan"),
split_cmnd( split_cmnd("occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found"),
"occ config:app:set files_antivirus av_icap_response_header --value=X-Infection-Found",
),
split_cmnd("occ security:certificates:import data/icap-cert.pem"), split_cmnd("occ security:certificates:import data/icap-cert.pem"),
]) ])
} }

View file

@ -1,12 +1,12 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::config::ContainerCreateBody; use bollard::config::ContainerCreateBody;
use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -1,133 +0,0 @@
use std::io::Stdout;
use crate::Result;
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::database::DatabaseFamily;
use crate::exec::exec;
use crate::image::pull_image;
use crate::mapping::Mapping;
use crate::service::ServiceTrait;
use bollard::Docker;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions;
use maplit::hashmap;
use miette::IntoDiagnostic;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DbGate;
#[async_trait::async_trait]
impl ServiceTrait for DbGate {
fn name(&self) -> &str {
"db"
}
fn hidden(&self) -> bool {
true
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
config: &HazeConfig,
options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "dbgate/dbgate:7.3.1";
pull_image(docker, image).await?;
let container_options = Some(CreateContainerOptions {
name: self.container_name(cloud_id),
..CreateContainerOptions::default()
});
let db_family = options.db.family();
let mut env = vec![
"CONNECTIONS=con1".into(),
"USAGE_ANALYTICS=false".into(),
"LOCAL_DBGATE_CLOUD=1".into(),
"LABEL_con1=haze".into(),
format!("ENGINE_con1={}", db_family.db_gate_driver()),
];
if db_family == DatabaseFamily::Sqlite {
env.extend(["FILE_con1=/haze-data/haze.db".into()]);
} else {
env.extend([
"SERVER_con1=db".into(),
format!("USER_con1={}", db_family.username()),
format!("PORT_con1={}", db_family.port()),
format!("DATABASE_con1={}", db_family.db()),
"PASSWORD_con1=haze".into(),
]);
}
if db_family == DatabaseFamily::Oracle {
env.push("SERVICE_NAME_con1=xe".into());
}
let mapping = Mapping::new(
crate::mapping::MappingSourceType::WorkDir,
"data",
"/haze-data",
);
let config = ContainerCreateBody {
image: Some(image.into()),
host_config: Some(HostConfig {
network_mode: Some(network.to_string()),
binds: Some(vec![
mapping
.get_volume_arg(cloud_id, config, &config.sources_root)
.unwrap(),
]),
..Default::default()
}),
env: Some(env),
labels: Some(hashmap! {
"haze-type".into() => self.name().into(),
"haze-cloud-id".into() => cloud_id.into(),
}),
networking_config: Some(NetworkingConfig {
endpoints_config: Some(hashmap! {
network.into() => EndpointSettings {
aliases: Some(vec![self.name().to_string()]),
..Default::default()
}
}),
}),
..Default::default()
};
let id = docker
.create_container(container_options, config)
.await
.into_diagnostic()?
.id;
docker.start_container(&id, None).await.into_diagnostic()?;
exec(
docker,
&id,
"root",
vec![
"sed",
"-i",
"s/attr(t,`title`,`Upgrade to Premium`)/attr(t,`style`,`display:none`)/",
"public/build/bundle.js",
],
Vec::<String>::new(),
None::<Stdout>,
)
.await?;
Ok(vec![id])
}
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-db-gate", cloud_id))
}
fn proxy_port(&self) -> u16 {
3000
}
}

View file

@ -1,12 +1,12 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::config::NetworkingConfig; use bollard::config::NetworkingConfig;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -1,14 +1,14 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::{image_exists, pull_image}; use crate::image::{image_exists, pull_image};
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, bail}; use miette::{bail, IntoDiagnostic};
use std::io::Stdout; use std::io::Stdout;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]

View file

@ -1,12 +1,12 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, ProxyConfig}; use crate::config::{HazeConfig, ProxyConfig};
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::config::NetworkingConfig; use bollard::config::NetworkingConfig;
use bollard::models::{ContainerCreateBody, ContainerState, EndpointSettings, HostConfig}; use bollard::models::{ContainerCreateBody, ContainerState, EndpointSettings, HostConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Report}; use miette::{IntoDiagnostic, Report};
use std::net::IpAddr; use std::net::IpAddr;
@ -102,9 +102,7 @@ impl ServiceTrait for Ldap {
split_cmnd("occ ldap:set-config s01 ldapBase dc=example,dc=org"), split_cmnd("occ ldap:set-config s01 ldapBase dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapBaseUsers dc=example,dc=org"), split_cmnd("occ ldap:set-config s01 ldapBaseUsers dc=example,dc=org"),
split_cmnd("occ ldap:set-config s01 ldapBaseGroups dc=example,dc=org"), split_cmnd("occ ldap:set-config s01 ldapBaseGroups dc=example,dc=org"),
split_cmnd( split_cmnd("occ ldap:set-config s01 ldapLoginFilter (&(&(objectclass=inetOrgPerson))(uid=%uid))"),
"occ ldap:set-config s01 ldapLoginFilter (&(&(objectclass=inetOrgPerson))(uid=%uid))",
),
split_cmnd("occ ldap:set-config s01 ldapUserFilter ((objectclass=inetOrgPerson))"), split_cmnd("occ ldap:set-config s01 ldapUserFilter ((objectclass=inetOrgPerson))"),
split_cmnd("occ ldap:set-config s01 ldapUserFilterMode 0"), split_cmnd("occ ldap:set-config s01 ldapUserFilterMode 0"),
split_cmnd("occ ldap:set-config s01 ldapUserDisplayName sn"), split_cmnd("occ ldap:set-config s01 ldapUserDisplayName sn"),

View file

@ -1,11 +1,11 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -1,14 +1,14 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ use bollard::models::{
ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig,
}; };
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, WrapErr}; use miette::{IntoDiagnostic, WrapErr};
use serde_json::Value; use serde_json::Value;
@ -260,6 +260,17 @@ impl ServiceTrait for ObjectStore {
split_cmnd("occ files_external:config 1 secret minio123"), split_cmnd("occ files_external:config 1 secret minio123"),
split_cmnd("mc alias set s3 http://s3:9000 minio minio123"), split_cmnd("mc alias set s3 http://s3:9000 minio minio123"),
]), ]),
// ObjectStore::S3s => Ok(vec![
// "occ files_external:create s3 amazons3 amazons3::accesskey".into(),
// "occ files_external:config 1 bucket ext".into(),
// "occ files_external:config 1 hostname s3".into(),
// "occ files_external:config 1 port 9000".into(),
// "occ files_external:config 1 use_ssl true".into(),
// "occ files_external:config 1 use_path_style true".into(),
// "occ files_external:config 1 key minio".into(),
// "occ files_external:config 1 secret minio123".into(),
// "mc alias set s3 https://s3:9000 minio minio123".into(),
// ]),
_ => Ok(Vec::new()), _ => Ok(Vec::new()),
} }
} }

View file

@ -1,13 +1,13 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use bollard::Docker; use crate::Result;
use bollard::config::NetworkingConfig; use bollard::config::NetworkingConfig;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;
use std::io::Stdout; use std::io::Stdout;

View file

@ -1,13 +1,13 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use bollard::Docker; use crate::Result;
use bollard::models::{ use bollard::models::{
ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig,
}; };
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Report}; use miette::{IntoDiagnostic, Report};

View file

@ -1,14 +1,14 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::exec::exec; use crate::exec::exec;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ use bollard::models::{
ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig,
}; };
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Report}; use miette::{IntoDiagnostic, Report};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;

View file

@ -1,11 +1,11 @@
use std::io::stdout; use std::io::stdout;
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::database::DatabaseFamily; use crate::database::DatabaseFamily;
use crate::exec::exec; use crate::exec::exec;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use crate::Result;
use bollard::Docker; use bollard::Docker;
use miette::Report; use miette::Report;

View file

@ -2,9 +2,9 @@ use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use bollard::Docker;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use local_ip_address::list_afinet_netifas; use local_ip_address::list_afinet_netifas;
use maplit::hashmap; use maplit::hashmap;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};

View file

@ -1,11 +1,11 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -1,11 +1,11 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::{Context, IntoDiagnostic}; use miette::{Context, IntoDiagnostic};
use std::fs::{create_dir_all, write}; use std::fs::{create_dir_all, write};

View file

@ -1,14 +1,14 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::database::DatabaseFamily; use crate::database::DatabaseFamily;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use crate::Result;
use bollard::Docker; use bollard::Docker;
use futures_util::future::try_join_all; use futures_util::future::try_join_all;
use maplit::hashmap; use maplit::hashmap;
use miette::Report; use miette::Report;
use serde_json::Value;
use serde_json::json; use serde_json::json;
use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::identity; use std::convert::identity;

View file

@ -1,11 +1,11 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::{ServiceTrait, split_cmnd}; use crate::service::{split_cmnd, ServiceTrait};
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -1,11 +1,11 @@
use crate::Result;
use crate::cloud::CloudOptions; use crate::cloud::CloudOptions;
use crate::config::HazeConfig; use crate::config::HazeConfig;
use crate::image::pull_image; use crate::image::pull_image;
use crate::service::ServiceTrait; use crate::service::ServiceTrait;
use bollard::Docker; use crate::Result;
use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig}; use bollard::models::{ContainerCreateBody, EndpointSettings, HostConfig, NetworkingConfig};
use bollard::query_parameters::CreateContainerOptions; use bollard::query_parameters::CreateContainerOptions;
use bollard::Docker;
use maplit::hashmap; use maplit::hashmap;
use miette::IntoDiagnostic; use miette::IntoDiagnostic;

View file

@ -10,8 +10,8 @@ use std::io::Cursor;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use tokio::fs::create_dir_all; use tokio::fs::create_dir_all;
use zip::ZipArchive;
use zip::read::root_dir_common_filter; use zip::read::root_dir_common_filter;
use zip::ZipArchive;
pub struct Sources { pub struct Sources {
#[allow(dead_code)] #[allow(dead_code)]