101 lines
2.6 KiB
Rust
101 lines
2.6 KiB
Rust
use async_std::fs::read_to_string;
|
|
use async_std::path::PathBuf;
|
|
use async_std::stream::StreamExt;
|
|
use axum::extract::State;
|
|
use axum::{routing::get, Router};
|
|
use serde::Serialize;
|
|
use std::collections::HashSet;
|
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
|
|
|
#[derive(Clone)]
|
|
struct AppState {
|
|
dokku_root: PathBuf,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct DokkuApp {
|
|
name: String,
|
|
hosts: HashSet<String>,
|
|
}
|
|
|
|
fn get_dokku_root() -> Option<std::path::PathBuf> {
|
|
// First, check if we have a custom directory
|
|
if let Some(dokku_root) = std::env::var_os("DOKKU_ROOT") {
|
|
return Some(std::path::PathBuf::from(dokku_root));
|
|
}
|
|
|
|
// Next, check for a dokku user and its home directory
|
|
match nix::unistd::User::from_name("dokku") {
|
|
Ok(Some(user)) => return Some(user.dir),
|
|
|
|
// If the user doesn't exist, do nothing
|
|
Ok(None) => return None,
|
|
|
|
// If there was an error, give up
|
|
_ => {}
|
|
}
|
|
|
|
// Finally, try a hard-coded path
|
|
let dokku_home_guess = std::path::PathBuf::from("/home/dokku");
|
|
if dokku_home_guess.is_dir() {
|
|
return Some(dokku_home_guess);
|
|
}
|
|
|
|
// If there's nothing else, give up
|
|
None
|
|
}
|
|
|
|
async fn hosts(State(state): State<AppState>) -> axum::Json<Vec<DokkuApp>> {
|
|
let mut dir_entries = state.dokku_root.read_dir().await.unwrap();
|
|
|
|
let mut apps: Vec<DokkuApp> = Vec::new();
|
|
|
|
while let Some(Ok(dir_entry)) = dir_entries.next().await {
|
|
let path = dir_entry.path();
|
|
|
|
let vhost_path = path.join("VHOST");
|
|
|
|
if !vhost_path.is_file().await {
|
|
continue;
|
|
}
|
|
|
|
let current_vhosts = read_to_string(&vhost_path).await.unwrap();
|
|
|
|
if current_vhosts.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
apps.push(DokkuApp {
|
|
name: String::from(path.file_name().unwrap().to_str().unwrap()),
|
|
hosts: current_vhosts.lines().map(String::from).collect(),
|
|
});
|
|
}
|
|
axum::Json(apps)
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let dokku_root = match get_dokku_root() {
|
|
Some(path) => path,
|
|
None => {
|
|
println!("Failed to find dokku root");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
println!("Using dokku root: {}", dokku_root.display());
|
|
|
|
let state = AppState {
|
|
dokku_root: PathBuf::from(dokku_root),
|
|
};
|
|
|
|
let app = Router::new().route("/hosts", get(hosts)).with_state(state);
|
|
|
|
let addr = &SocketAddr::new(IpAddr::from(Ipv6Addr::UNSPECIFIED), 3000);
|
|
println!("Listening on {addr}");
|
|
axum::Server::bind(addr)
|
|
.serve(app.into_make_service())
|
|
.await
|
|
.unwrap();
|
|
}
|