Compare commits
No commits in common. "main" and "main" have entirely different histories.
9 changed files with 186 additions and 155 deletions
200
README.md
200
README.md
|
@ -1,36 +1,7 @@
|
|||
# Freedive
|
||||
Dive into FreeBSD with `Freedive`!
|
||||
|
||||
Freedive emerges as an innovative standalone tool for FreeBSD,
|
||||
offering a graphical user interface (GUI) that revolutionizes
|
||||
the way systems are managed,
|
||||
enabling both newcomers and experienced users
|
||||
to administer their FreeBSD installations with ease.
|
||||
|
||||
This GUI, accessible through a mobile-friendly web interface,
|
||||
democratizes the use of FreeBSD by allowing beginners
|
||||
to engage with the operating system
|
||||
without the need to delve into the complexities of the command-line shell.
|
||||
Designed with responsiveness in mind,
|
||||
Freedive's interface adapts seamlessly to various screen sizes,
|
||||
ensuring that system administrators can perform essential tasks
|
||||
from virtually any device, be it a smartphone, tablet, or desktop.
|
||||
|
||||
The intuitive design of Freedive's web interface lowers
|
||||
the barrier to entry for managing FreeBSD systems,
|
||||
making it more approachable for users
|
||||
who may not be familiar with Unix-like environments.
|
||||
By providing a full spectrum of
|
||||
system management capabilities through its GUI,
|
||||
Freedive empowers users to perform tasks
|
||||
ranging from user account management to service configuration,
|
||||
all without writing a single line of shell code.
|
||||
|
||||
Freedive stands as a testament to the versatility of FreeBSD,
|
||||
extending its appeal beyond the traditional tech-savvy audience
|
||||
to a broader range of users who can now leverage
|
||||
the power of FreeBSD
|
||||
with the convenience of a modern web-based interface.
|
||||
Freedive emerges as an innovative standalone tool for FreeBSD, offering a graphical user interface (GUI) that revolutionizes the way systems are managed, enabling both newcomers and experienced users to administer their FreeBSD installations with ease. This GUI, accessible through a mobile-friendly web interface, democratizes the use of FreeBSD by allowing beginners to engage with the operating system without the need to delve into the complexities of the command-line shell. Designed with responsiveness in mind, Freedive's interface adapts seamlessly to various screen sizes, ensuring that system administrators can perform essential tasks from virtually any device, be it a smartphone, tablet, or desktop. The intuitive design of Freedive's web interface lowers the entry barrier for managing FreeBSD systems, making it more approachable for users who may not be familiar with Unix-like environments. By providing a full spectrum of system management capabilities through its GUI, Freedive empowers users to perform tasks ranging from user account management to service configuration, all without writing a single line of shell code. Freedive stands as a testament to the versatility of FreeBSD, extending its appeal beyond the traditional tech-savvy audience to a broader range of users who can now leverage the power of FreeBSD with the convenience of a modern web-based interface.
|
||||
|
||||
## Features
|
||||
Made by and for users of FreeBSD as their:
|
||||
|
@ -68,24 +39,80 @@ Freedive works in immediate mode:
|
|||
|
||||
|
||||
## Quick Steps
|
||||
### Binaries
|
||||
For a quick test you can just use the generated FreeBSD pkgs to try Freedive:
|
||||
* [freedive-0.1.0.pkg](https://cdn.gyptazy.ch/files/amd64/freebsd/freedive/freedive-0.1.0.pkg) [hosted at gyptazy.ch]
|
||||
|
||||
### Get pre-built package
|
||||
### Build & Deploy
|
||||
#### Requirements
|
||||
Building Freedive requires some additional packages like gmake, gcc and especially elixir (>= 1.16.0). This also requires you to switch to the latest ports/pkgs branch instead of the quarterly.
|
||||
|
||||
Try Freedive on FreeBSD 14.0-RELEASE and newer:
|
||||
* amd64: [freedive-0.1.0.pkg](https://cdn.gyptazy.ch/files/amd64/freebsd/freedive/freedive-0.1.0.pkg) [hosted at gyptazy.ch]
|
||||
* arm64: [freedive-0.1.0_arm64.pkg](https://cdn.gyptazy.ch/files/arm64/freebsd/freedive/freedive-0.1.0_arm64.pkg) [hosted at gyptazy.ch]
|
||||
This is not needed when you are already using the `latest` repository. However, switching to the latest ports/pkgs branch can be easily done with the following steps:
|
||||
```
|
||||
mkdir -p /usr/local/etc/pkg/repos
|
||||
echo "FreeBSD: { url: \"pkg+http://pkg.freebsd.org/\${ABI}/latest\" }" > /usr/local/etc/pkg/repos/FreeBSD.conf
|
||||
pkg -y upgrade -f
|
||||
```
|
||||
|
||||
If you are fully running on pkg instead of ports you need to copy a file which is referenced and used by Freedive. If you have
|
||||
ports active on your system, this step can be skipped - if not, simply run the following commands:
|
||||
```
|
||||
mkdir -p /usr/ports/Keywords/
|
||||
curl -O /usr/ports/Keywords/ https://raw.githubusercontent.com/freebsd/freebsd-ports/main/Keywords/sample.ucl
|
||||
```
|
||||
|
||||
You may also want to setup your own doas account (passwordless) by running:
|
||||
```
|
||||
echo "permit nopass <USERNAME> as root" >> /usr/local/etc/doas.conf
|
||||
```
|
||||
|
||||
Within the last step, the needed dependencies can be installed from the pkg repository:
|
||||
```
|
||||
pkg install git inotify-tools gmake elixir gcc doas
|
||||
```
|
||||
|
||||
#### Building
|
||||
Freedive can be simply build by running the following commands:
|
||||
```
|
||||
pkg install -y git inotify-tools gmake elixir gcc doas
|
||||
git clone https://brew.bsd.cafe/hiway/freedive.git
|
||||
mix setup
|
||||
```
|
||||
|
||||
#### Packaging
|
||||
If you also want to create and build a distributable `.pkg` file, simply run the following command afterwards:
|
||||
```
|
||||
mix package
|
||||
```
|
||||
This creates you the file `freedive-0.1.0.pkg` within your build directory.
|
||||
|
||||
|
||||
### Install package
|
||||
### Usage (compile)
|
||||
After building, you can directly start Freedive. Freedive can be started by running `mix` or inside `IEx` by executing:
|
||||
|
||||
```
|
||||
mix phx.server
|
||||
or
|
||||
iex -S mix phx.server
|
||||
```
|
||||
|
||||
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
> Registering accounts via browser is enabled in dev envronment.
|
||||
|
||||
Visit [/users/register](http://localhost:4000/users/register) to create an account.
|
||||
Then visit [/dev/mailbox](http://localhost:4000/dev/mailbox) to confirm the account.
|
||||
|
||||
Now, you can ue these credentials to log in and explore Freedive.
|
||||
|
||||
### Usage (pkg)
|
||||
Another solution directly leads into using the built package. This can be installed by running:
|
||||
|
||||
```
|
||||
pkg install -U -y freedive-0.1.0.pkg
|
||||
```
|
||||
|
||||
Next, edit the configuration at `/usr/local/etc/freedive/freedive.env`
|
||||
and define following settings:
|
||||
Afterwards, the configuration should be adjusted in `/usr/local/etc/freedive/freedive.env` where the following settings should be defined:
|
||||
|
||||
```
|
||||
HOST="hostname"
|
||||
|
@ -93,10 +120,10 @@ BIND="ip-to-bind"
|
|||
PORT=3443
|
||||
```
|
||||
|
||||
Ensure that the host is set to the hostname you'll use
|
||||
Ensure that the host is set to whatever hostname you'll use
|
||||
to access the service from browser.
|
||||
For example, if you bind to the Tailscale/Wireguard IP,
|
||||
use the hostname that points to this IP.
|
||||
use the hostname that'll point to this IP.
|
||||
Port can be anything suitable in your environmet
|
||||
that's open and accessible from your mobile/laptop.
|
||||
|
||||
|
@ -124,100 +151,11 @@ Check logs
|
|||
|
||||
- `tail -f /var/log/freedive/freedive.log`
|
||||
|
||||
|
||||
Visit `https://hostname:port` from your browser and
|
||||
Visit https://hostname:port from your browser and
|
||||
log in with the account created above.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Requirements
|
||||
|
||||
If you have `ports` active on your system, this step can be skipped.
|
||||
If you have not installed `ports` (the `/usr/ports` directory is missing)
|
||||
then you need to copy a file which is referenced and used
|
||||
by Freedive when building its own package.
|
||||
|
||||
Run the following commands:
|
||||
|
||||
```
|
||||
mkdir -p /usr/ports/Keywords/
|
||||
curl -O /usr/ports/Keywords/ https://raw.githubusercontent.com/freebsd/freebsd-ports/main/Keywords/sample.ucl
|
||||
```
|
||||
|
||||
You also want to setup your own doas account (passwordless) by running:
|
||||
|
||||
```
|
||||
echo "permit nopass <USERNAME> as root" >> /usr/local/etc/doas.conf
|
||||
```
|
||||
|
||||
Freedive will only function as expected if `doas` is installed
|
||||
and configured to allow the user running the server
|
||||
to execute commands as root without requiring a password.
|
||||
|
||||
|
||||
### Development Environment
|
||||
|
||||
Install build tools and depedencies
|
||||
```
|
||||
pkg install -y ca_root_nss doas elixir gcc git gmake inotify-tools
|
||||
```
|
||||
|
||||
Get code
|
||||
```
|
||||
git clone https://brew.bsd.cafe/hiway/freedive.git
|
||||
cd freedive
|
||||
```
|
||||
|
||||
Setup dev environment
|
||||
```
|
||||
mix setup
|
||||
```
|
||||
|
||||
> It is safe to ignore warning about `mix_freebsd_pkg` requiring Elixir 1.6, it will go away
|
||||
> when Elixir 1.6 becomes available in quarterly packages.
|
||||
|
||||
|
||||
### Development Server
|
||||
|
||||
Freedive dev server can be started by running `mix` or inside `IEx` by executing:
|
||||
|
||||
```
|
||||
mix phx.server
|
||||
|
||||
# or
|
||||
|
||||
iex -S mix phx.server
|
||||
```
|
||||
|
||||
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
> Note:
|
||||
> Registering accounts via browser is enabled in dev environment.
|
||||
|
||||
Visit [/users/register](http://localhost:4000/users/register) to create an account.
|
||||
Then visit [/dev/mailbox](http://localhost:4000/dev/mailbox) to confirm the account.
|
||||
|
||||
Now, you can ue these credentials to log in and explore Freedive.
|
||||
|
||||
|
||||
### Packaging
|
||||
|
||||
Create and build a distributable `.pkg` file that can be installed on target machines.
|
||||
|
||||
Run the following command in `freedive` directory:
|
||||
|
||||
```
|
||||
mix package
|
||||
```
|
||||
|
||||
This creates the file `freedive-0.x.x.pkg` within your project directory.
|
||||
|
||||
|
||||
## Chat / Community
|
||||
|
||||
The chat and community are on Matrix hosted at the BSD.cafe:
|
||||
The chat and community is based in the Matrix channel hosted at the BSD.cafe:
|
||||
* #BSDCafe:bsd.cafe
|
||||
|
||||
You can join by clicking: https://matrix.to/#/#BSDCafe:bsd.cafe
|
||||
You can also join by simply clicking the following link: https://matrix.to/#/#BSDCafe:bsd.cafe
|
|
@ -1,4 +1,7 @@
|
|||
@import "bulma/css/bulma.min.css";
|
||||
/* @import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities"; */
|
||||
@import "bulma";
|
||||
|
||||
.size-256 {
|
||||
height: 256px;
|
||||
|
@ -7,4 +10,4 @@
|
|||
|
||||
.is-fullwidth {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
// We import the CSS which is extracted to its own file by esbuild.
|
||||
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
|
||||
import "../css/app.css"
|
||||
|
||||
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
|
||||
// to get started and then uncomment the line below.
|
||||
// import "./user_socket.js"
|
||||
|
|
75
assets/tailwind.config.js
Normal file
75
assets/tailwind.config.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
// See the Tailwind configuration guide for advanced usage
|
||||
// https://tailwindcss.com/docs/configuration
|
||||
|
||||
const plugin = require("tailwindcss/plugin")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./js/**/*.js",
|
||||
"../lib/freedive_web.ex",
|
||||
"../lib/freedive_web/**/*.*ex"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: "#FD4F00",
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
// Allows prefixing tailwind classes with LiveView classes to add rules
|
||||
// only when LiveView classes are applied, for example:
|
||||
//
|
||||
// <div class="phx-click-loading:animate-ping">
|
||||
//
|
||||
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
||||
|
||||
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
|
||||
// See your `CoreComponents.icon/1` for more information.
|
||||
//
|
||||
plugin(function({matchComponents, theme}) {
|
||||
let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
|
||||
let values = {}
|
||||
let icons = [
|
||||
["", "/24/outline"],
|
||||
["-solid", "/24/solid"],
|
||||
["-mini", "/20/solid"],
|
||||
["-micro", "/16/solid"]
|
||||
]
|
||||
icons.forEach(([suffix, dir]) => {
|
||||
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
|
||||
let name = path.basename(file, ".svg") + suffix
|
||||
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
|
||||
})
|
||||
})
|
||||
matchComponents({
|
||||
"hero": ({name, fullPath}) => {
|
||||
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
|
||||
let size = theme("spacing.6")
|
||||
if (name.endsWith("-mini")) {
|
||||
size = theme("spacing.5")
|
||||
} else if (name.endsWith("-micro")) {
|
||||
size = theme("spacing.4")
|
||||
}
|
||||
return {
|
||||
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
|
||||
"-webkit-mask": `var(--hero-${name})`,
|
||||
"mask": `var(--hero-${name})`,
|
||||
"mask-repeat": "no-repeat",
|
||||
"background-color": "currentColor",
|
||||
"vertical-align": "middle",
|
||||
"display": "inline-block",
|
||||
"width": size,
|
||||
"height": size
|
||||
}
|
||||
}
|
||||
}, {values})
|
||||
})
|
||||
]
|
||||
}
|
|
@ -47,6 +47,18 @@ config :esbuild,
|
|||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
||||
]
|
||||
|
||||
# Configure tailwind (the version is required)
|
||||
config :tailwind,
|
||||
version: "3.4.0",
|
||||
freedive: [
|
||||
args: ~w(
|
||||
--config=tailwind.config.js
|
||||
--input=css/app.css
|
||||
--output=../priv/static/assets/app.css
|
||||
),
|
||||
cd: Path.expand("../assets", __DIR__)
|
||||
]
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
|
|
|
@ -23,6 +23,7 @@ config :freedive, FreediveWeb.Endpoint,
|
|||
secret_key_base: "gccoUoyi8/eCAM6QwMnufYsgYUtubBHBnmMQZ6xYpIIYI26Es4S/3VpIw6UKT0Ll",
|
||||
watchers: [
|
||||
esbuild: {Esbuild, :install_and_run, [:freedive, ~w(--sourcemap=inline --watch)]},
|
||||
tailwind: {Tailwind, :install_and_run, [:freedive, ~w(--watch)]}
|
||||
]
|
||||
|
||||
# ## SSL Support
|
||||
|
|
|
@ -18,28 +18,28 @@ defmodule Freedive.Api.Command do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> Freedive.Api.Command.execute!("whoami", [], doas: true)
|
||||
iex> Freedive.execute!("whoami", [], doas: true)
|
||||
"root"
|
||||
|
||||
iex> Freedive.Api.Command.execute!("whoami", [], doas: "www")
|
||||
iex> Freedive.execute!("whoami", [], doas: "www")
|
||||
"www"
|
||||
|
||||
iex> Freedive.Api.Command.execute!("whoami", [], jail: "testjail")
|
||||
iex> Freedive.execute!("whoami", [], jail: "testjail")
|
||||
"root"
|
||||
|
||||
iex> Freedive.Api.Command.execute!("whoami", [], jail: "testjail", doas: "operator")
|
||||
iex> Freedive.execute!("whoami", [], jail: "testjail", doas: "operator")
|
||||
"operator"
|
||||
|
||||
iex> Freedive.Api.Command.execute("hostname", [], jail: "testjail")
|
||||
iex> Freedive.execute("hostname", [], jail: "testjail")
|
||||
{:ok, "testjail"}
|
||||
|
||||
iex> Freedive.Api.Command.execute!("sysctl", ["-n", "security.jail.jailed"])
|
||||
iex> Freedive.execute!("sysctl", ["-n", "security.jail.jailed"])
|
||||
"0"
|
||||
|
||||
iex> Freedive.Api.Command.execute!("sysctl", ["-n", "security.jail.jailed"], jail: "testjail")
|
||||
iex> Freedive.execute!("sysctl", ["-n", "security.jail.jailed"], jail: "testjail")
|
||||
"1"
|
||||
|
||||
iex> Freedive.Api.Command.execute!("printenv", ["FOO"], jail: "testjail", env: [{"FOO", "bar"}])
|
||||
iex> Freedive.execute!("printenv", ["FOO"], jail: "testjail", env: [{"FOO", "bar"}])
|
||||
"bar"
|
||||
"""
|
||||
def execute!(command, args, opts \\ []), do: raise_on_error(execute(command, args, opts))
|
||||
|
@ -49,28 +49,28 @@ defmodule Freedive.Api.Command do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> Freedive.Api.Command.execute("whoami", [], doas: true)
|
||||
iex> Freedive.execute("whoami", [], doas: true)
|
||||
{:ok, "root"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("whoami", [], doas: "www")
|
||||
iex> Freedive.execute("whoami", [], doas: "www")
|
||||
{:ok, "www"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("whoami", [], jail: "testjail")
|
||||
iex> Freedive.execute("whoami", [], jail: "testjail")
|
||||
{:ok, "root"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("whoami", [], jail: "testjail", doas: "operator")
|
||||
iex> Freedive.execute("whoami", [], jail: "testjail", doas: "operator")
|
||||
{:ok, "operator"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("hostname", [], jail: "testjail")
|
||||
iex> Freedive.execute("hostname", [], jail: "testjail")
|
||||
{:ok, "testjail"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("sysctl", ["-n", "security.jail.jailed"])
|
||||
iex> Freedive.execute("sysctl", ["-n", "security.jail.jailed"])
|
||||
{:ok, "0"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("sysctl", ["-n", "security.jail.jailed"], jail: "testjail")
|
||||
iex> Freedive.execute("sysctl", ["-n", "security.jail.jailed"], jail: "testjail")
|
||||
{:ok, "1"}
|
||||
|
||||
iex> Freedive.Api.Command.execute("printenv", ["FOO"], jail: "testjail", env: [{"FOO", "bar"}])
|
||||
iex> Freedive.execute("printenv", ["FOO"], jail: "testjail", env: [{"FOO", "bar"}])
|
||||
{:ok, "bar"}
|
||||
"""
|
||||
@spec execute(String.t(), list(String.t()), Keyword.t()) ::
|
||||
|
|
8
mix.exs
8
mix.exs
|
@ -53,6 +53,7 @@ defmodule Freedive.MixProject do
|
|||
{:floki, ">= 0.30.0", only: :test},
|
||||
{:phoenix_live_dashboard, "~> 0.8.3"},
|
||||
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
|
||||
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
{:heroicons,
|
||||
github: "tailwindlabs/heroicons",
|
||||
tag: "v2.1.1",
|
||||
|
@ -68,6 +69,7 @@ defmodule Freedive.MixProject do
|
|||
{:jason, "~> 1.2"},
|
||||
{:dns_cluster, "~> 0.1.1"},
|
||||
{:bandit, "~> 1.2"},
|
||||
{:phx_tailwind_freebsd, "~> 0.2.1", runtime: Mix.env() == :dev},
|
||||
{:lucide_icons, "~> 1.1"},
|
||||
{:phx_component_helpers, "~> 1.4"},
|
||||
{:mix_freebsd_pkg, github: "hiway/mix_freebsd_pkg", runtime: Mix.env() == :dev},
|
||||
|
@ -86,6 +88,7 @@ defmodule Freedive.MixProject do
|
|||
[
|
||||
setup: [
|
||||
"deps.get",
|
||||
"tailwind.install_freebsd",
|
||||
"ecto.setup",
|
||||
"assets.setup",
|
||||
"assets.build"
|
||||
|
@ -93,9 +96,10 @@ defmodule Freedive.MixProject do
|
|||
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
||||
"assets.setup": ["esbuild.install --if-missing"],
|
||||
"assets.build": ["esbuild freedive"],
|
||||
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
||||
"assets.build": ["tailwind freedive", "esbuild freedive"],
|
||||
"assets.deploy": [
|
||||
"tailwind freedive --minify",
|
||||
"esbuild freedive --minify",
|
||||
"phx.digest"
|
||||
],
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -39,10 +39,12 @@
|
|||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"phx_component_helpers": {:hex, :phx_component_helpers, "1.4.1", "dbbc8ed3082055a901e3918cb24ec43df64f0f3bb81d6865beaf114fb355569e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, ">= 4.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.18.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "cf499d28f2af3c8398230994c54d2ea86cd4759523693b6b75b9897cc7f57912"},
|
||||
"phx_config_util": {:hex, :phx_config_util, "0.1.0", "185e3435e8fa0d18113d153ce6d40cc7e865ee7f1dfd27e66b76bc612ed681fd", [:mix], [{:net_address, "~> 0.3.1", [hex: :net_address, repo: "hexpm", optional: false]}], "hexpm", "fe0d303d9716875f4d586a706d39caf4a374dafb0e0adff63dd1e0cd25117133"},
|
||||
"phx_tailwind_freebsd": {:hex, :phx_tailwind_freebsd, "0.2.1", "23583bb200196f879fbedd49be69b4d2d7ad2137971ad9a060f2cf38358c14a8", [:mix], [], "hexpm", "04aabe4b93ba850ca9116ba0a0cf302cbc5b846d09d0e268213a4553a9c53b31"},
|
||||
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"retry": {:hex, :retry, "0.18.0", "dc58ebe22c95aa00bc2459f9e0c5400e6005541cf8539925af0aa027dc860543", [:mix], [], "hexpm", "9483959cc7bf69c9e576d9dfb2b678b71c045d3e6f39ab7c9aa1489df4492d73"},
|
||||
"swoosh": {:hex, :swoosh, "1.16.5", "5742f24c4d081671ebe87d8e7f6595cf75205d7f808cc5d55b09e4598b583413", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.1.0", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2324cf696b09ee52e5e1049dcc77880a11fe618a381e2df1c5ca5d69c380eb0"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
||||
|
|
Loading…
Reference in a new issue