root: introduce allinone mode (#21990)

This commit is contained in:
Marc 'risson' Schmitt
2026-05-04 16:43:11 +02:00
committed by GitHub
parent 82fc2e2c80
commit ba62507fc2
12 changed files with 212 additions and 79 deletions
+5
View File
@@ -229,6 +229,11 @@ source_docs/
### Golang ### ### Golang ###
/vendor/ /vendor/
server
proxy
ldap
rac
radius
### Docker ### ### Docker ###
tests/openid_conformance/exports/*.zip tests/openid_conformance/exports/*.zip
Generated
+10
View File
@@ -191,6 +191,7 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"uuid", "uuid",
"which",
] ]
[[package]] [[package]]
@@ -4512,6 +4513,15 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "which"
version = "8.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.1" version = "1.6.1"
+2
View File
@@ -113,6 +113,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
] } ] }
url = "= 2.5.8" url = "= 2.5.8"
uuid = { version = "= 1.23.1", features = ["serde", "v4"] } uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
which = "= 8.0.2"
ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" } ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" }
ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" } ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" }
@@ -282,6 +283,7 @@ sqlx = { workspace = true, optional = true }
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true
uuid.workspace = true uuid.workspace = true
which.workspace = true
[lints] [lints]
workspace = true workspace = true
+4 -7
View File
@@ -109,14 +109,11 @@ i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that requir
aws-cfn: aws-cfn:
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
run-server: ## Run the main authentik server process run: ## Run the main authentik server and worker processes
$(UV) run ak server $(UV) run ak allinone
run-worker: ## Run the main authentik worker process run-watch: ## Run the authentik server and worker, with auto reloading
$(UV) run ak worker watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs,go --no-meta --notify -- $(UV) run ak allinone
run-worker-watch: ## Run the authentik worker, with auto reloading
watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- $(UV) run ak worker
core-i18n-extract: core-i18n-extract:
$(UV) run ak makemessages \ $(UV) run ak makemessages \
+2
View File
@@ -26,6 +26,8 @@ var healthcheckCmd = &cobra.Command{
exitCode := 1 exitCode := 1
log.WithField("mode", mode).Debug("checking health") log.WithField("mode", mode).Debug("checking health")
switch strings.ToLower(mode) { switch strings.ToLower(mode) {
case "allinone":
fallthrough
case "server": case "server":
exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path)) exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path))
case "worker": case "worker":
+2 -2
View File
@@ -31,7 +31,7 @@ function run_authentik {
echo go run ./cmd/server "$@" echo go run ./cmd/server "$@"
fi fi
;; ;;
worker) allinone | worker)
if [[ -x "$(command -v authentik)" ]]; then if [[ -x "$(command -v authentik)" ]]; then
echo authentik "$@" echo authentik "$@"
else else
@@ -105,7 +105,7 @@ elif [[ "$1" == "test-all" ]]; then
prepare_debug prepare_debug
chmod 777 /root chmod 777 /root
check_if_root_and_run manage test authentik check_if_root_and_run manage test authentik
elif [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then elif [[ "$1" == "allinone" ]] || [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then
wait_for_db wait_for_db
check_if_root_and_run "$@" check_if_root_and_run "$@"
elif [[ "$1" == "healthcheck" ]]; then elif [[ "$1" == "healthcheck" ]]; then
+23 -33
View File
@@ -1,6 +1,6 @@
//! Utilities to run an axum server. //! Utilities to run an axum server.
use std::{net, os::unix}; use std::{net, os::unix, path::PathBuf};
use ak_common::arbiter::{Arbiter, Tasks}; use ak_common::arbiter::{Arbiter, Tasks};
use axum::Router; use axum::Router;
@@ -21,26 +21,20 @@ async fn run_plain(
name: &str, name: &str,
router: Router, router: Router,
addr: net::SocketAddr, addr: net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
info!(addr = addr.to_string(), "starting {name} server"); info!(addr = addr.to_string(), "starting {name} server");
let handle = Handle::new(); let handle = Handle::new();
arbiter.add_net_handle(handle.clone()).await; arbiter.add_net_handle(handle.clone()).await;
let res = axum_server::Server::bind(addr) axum_server::Server::bind(addr)
.acceptor(CatchPanicAcceptor::new( .acceptor(CatchPanicAcceptor::new(
ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()), ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()),
arbiter.clone(), arbiter.clone(),
)) ))
.handle(handle) .handle(handle)
.serve(router.into_make_service_with_connect_info::<net::SocketAddr>()) .serve(router.into_make_service_with_connect_info::<net::SocketAddr>())
.await; .await?;
if res.is_err() && allow_failure {
arbiter.shutdown().await;
return Ok(());
}
res?;
Ok(()) Ok(())
} }
@@ -49,60 +43,59 @@ async fn run_plain(
/// ///
/// `name` is only used for observability purposes and should describe which module is starting the /// `name` is only used for observability purposes and should describe which module is starting the
/// server. /// server.
///
/// `allow_failure` allows the server to fail silently.
pub fn start_plain( pub fn start_plain(
tasks: &mut Tasks, tasks: &mut Tasks,
name: &'static str, name: &'static str,
router: Router, router: Router,
addr: net::SocketAddr, addr: net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
let arbiter = tasks.arbiter(); let arbiter = tasks.arbiter();
tasks tasks
.build_task() .build_task()
.name(&format!("{}::run_plain({name}, {addr})", module_path!())) .name(&format!("{}::run_plain({name}, {addr})", module_path!()))
.spawn(run_plain(arbiter, name, router, addr, allow_failure))?; .spawn(run_plain(arbiter, name, router, addr))?;
Ok(()) Ok(())
} }
struct UnixSocketGuard(PathBuf);
impl Drop for UnixSocketGuard {
fn drop(&mut self) {
trace!(path = ?self.0, "removing socket");
if let Err(err) = std::fs::remove_file(&self.0) {
trace!(?err, "failed to remove socket, ignoring");
}
}
}
pub(crate) async fn run_unix( pub(crate) async fn run_unix(
arbiter: Arbiter, arbiter: Arbiter,
name: &str, name: &str,
router: Router, router: Router,
addr: unix::net::SocketAddr, addr: unix::net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
info!(?addr, "starting {name} server"); info!(?addr, "starting {name} server");
let handle = Handle::new(); let handle = Handle::new();
arbiter.add_unix_handle(handle.clone()).await; arbiter.add_unix_handle(handle.clone()).await;
if !allow_failure && let Some(path) = addr.as_pathname() { let _guard = if let Some(path) = addr.as_pathname() {
trace!(?addr, "removing socket"); trace!(?addr, "removing socket");
if let Err(err) = std::fs::remove_file(path) { if let Err(err) = std::fs::remove_file(path) {
trace!(?err, "failed to remove socket, ignoring"); trace!(?err, "failed to remove socket, ignoring");
} }
} Some(UnixSocketGuard(path.to_owned()))
let res = axum_server::Server::bind(addr.clone()) } else {
None
};
axum_server::Server::bind(addr.clone())
.acceptor(CatchPanicAcceptor::new( .acceptor(CatchPanicAcceptor::new(
DefaultAcceptor::new(), DefaultAcceptor::new(),
arbiter.clone(), arbiter.clone(),
)) ))
.handle(handle) .handle(handle)
.serve(router.into_make_service()) .serve(router.into_make_service())
.await; .await?;
if !allow_failure && let Some(path) = addr.as_pathname() {
trace!(?addr, "removing socket");
if let Err(err) = std::fs::remove_file(path) {
trace!(?err, "failed to remove socket, ignoring");
}
}
if res.is_err() && allow_failure {
arbiter.shutdown().await;
return Ok(());
}
res?;
Ok(()) Ok(())
} }
@@ -111,20 +104,17 @@ pub(crate) async fn run_unix(
/// ///
/// `name` is only used for observability purposes and should describe which module is starting the /// `name` is only used for observability purposes and should describe which module is starting the
/// server. /// server.
///
/// `allow_failure` allows the server to fail silently.
pub fn start_unix( pub fn start_unix(
tasks: &mut Tasks, tasks: &mut Tasks,
name: &'static str, name: &'static str,
router: Router, router: Router,
addr: unix::net::SocketAddr, addr: unix::net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
let arbiter = tasks.arbiter(); let arbiter = tasks.arbiter();
tasks tasks
.build_task() .build_task()
.name(&format!("{}::run_unix({name}, {addr:?})", module_path!())) .name(&format!("{}::run_unix({name}, {addr:?})", module_path!()))
.spawn(run_unix(arbiter, name, router, addr, allow_failure))?; .spawn(run_unix(arbiter, name, router, addr))?;
Ok(()) Ok(())
} }
+27
View File
@@ -23,10 +23,23 @@ struct Cli {
#[derive(Debug, FromArgs, PartialEq)] #[derive(Debug, FromArgs, PartialEq)]
#[argh(subcommand)] #[argh(subcommand)]
enum Command { enum Command {
#[cfg(feature = "core")]
AllInOne(AllInOne),
#[cfg(feature = "core")]
Server(server::Cli),
#[cfg(feature = "core")] #[cfg(feature = "core")]
Worker(worker::Cli), Worker(worker::Cli),
} }
#[derive(Debug, FromArgs, PartialEq)]
/// Run both the authentik server and worker.
#[argh(subcommand, name = "allinone")]
#[expect(
clippy::empty_structs_with_brackets,
reason = "argh doesn't support unit structs"
)]
pub(crate) struct AllInOne {}
fn main() -> Result<()> { fn main() -> Result<()> {
let tracing_crude = ak_tracing::install_crude(); let tracing_crude = ak_tracing::install_crude();
info!(version = authentik_full_version(), "authentik is starting"); info!(version = authentik_full_version(), "authentik is starting");
@@ -34,6 +47,10 @@ fn main() -> Result<()> {
let cli: Cli = argh::from_env(); let cli: Cli = argh::from_env();
match &cli.command { match &cli.command {
#[cfg(feature = "core")]
Command::AllInOne(_) => Mode::set(Mode::AllInOne)?,
#[cfg(feature = "core")]
Command::Server(_) => Mode::set(Mode::Server)?,
#[cfg(feature = "core")] #[cfg(feature = "core")]
Command::Worker(_) => Mode::set(Mode::Worker)?, Command::Worker(_) => Mode::set(Mode::Worker)?,
} }
@@ -76,6 +93,16 @@ fn main() -> Result<()> {
} }
match cli.command { match cli.command {
#[cfg(feature = "core")]
Command::AllInOne(_) => {
server::start(server::Cli::default(), &mut tasks).await?;
let workers = worker::start(worker::Cli::default(), &mut tasks)?;
metrics.workers.store(Some(workers));
}
#[cfg(feature = "core")]
Command::Server(args) => {
server::start(args, &mut tasks).await?;
}
#[cfg(feature = "core")] #[cfg(feature = "core")]
Command::Worker(args) => { Command::Worker(args) => {
let workers = worker::start(args, &mut tasks)?; let workers = worker::start(args, &mut tasks)?;
+6 -10
View File
@@ -2,6 +2,7 @@ use std::{env::temp_dir, os::unix, path::PathBuf, sync::Arc};
use ak_axum::{router::wrap_router, server}; use ak_axum::{router::wrap_router, server};
use ak_common::{ use ak_common::{
Mode,
arbiter::{Arbiter, Tasks}, arbiter::{Arbiter, Tasks},
config, config,
}; };
@@ -77,15 +78,11 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
.name(&format!("{}::run_upkeep", module_path!())) .name(&format!("{}::run_upkeep", module_path!()))
.spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?; .spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?;
// Only run HTTP server in worker mode, in server or allinone mode, they're handled by the
// server.
if Mode::get() == Mode::Worker {
for addr in config::get().listen.metrics.iter().copied() { for addr in config::get().listen.metrics.iter().copied() {
server::start_plain( server::start_plain(tasks, "metrics", router.clone(), addr)?;
tasks,
"metrics",
router.clone(),
addr,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev */
)?;
} }
server::start_unix( server::start_unix(
@@ -93,9 +90,8 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
"metrics", "metrics",
router, router,
unix::net::SocketAddr::from_pathname(socket_path())?, unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same machine,
* like in dev */
)?; )?;
}
Ok(metrics) Ok(metrics)
} }
+120 -1
View File
@@ -1,5 +1,124 @@
use std::{env::temp_dir, path::PathBuf}; use std::{env::temp_dir, path::PathBuf, process::Stdio, sync::Arc};
use ak_common::{Arbiter, Tasks, config};
use argh::FromArgs;
use eyre::{Result, eyre};
use nix::{
sys::signal::{Signal, kill},
unistd::Pid,
};
use tokio::{
process::{Child, Command},
sync::Mutex,
time::{Duration, sleep, timeout},
};
use tracing::{info, warn};
#[derive(Debug, Default, FromArgs, PartialEq, Eq)]
/// Run the authentik server.
#[argh(subcommand, name = "server")]
#[expect(
clippy::empty_structs_with_brackets,
reason = "argh doesn't support unit structs"
)]
pub(crate) struct Cli {}
pub(crate) fn socket_path() -> PathBuf { pub(crate) fn socket_path() -> PathBuf {
temp_dir().join("authentik.sock") temp_dir().join("authentik.sock")
} }
pub(crate) struct Server {
server: Mutex<Child>,
}
impl Server {
async fn new() -> Result<Self> {
info!("starting server");
let server = if config::get().debug && which::which("authentik-server").is_err() {
let build_status = Command::new("go")
.args(["build", "-o", "server", "./cmd/server"])
.stdin(Stdio::null())
.status()
.await?;
if !build_status.success() {
return Err(eyre!("golang server failed to compile"));
}
Command::new("./server")
.kill_on_drop(true)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
} else {
Command::new("authentik-server")
.kill_on_drop(true)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
};
Ok(Self {
server: Mutex::new(server),
})
}
async fn shutdown(&self) -> Result<()> {
info!("shutting down server");
let mut server = self.server.lock().await;
if let Some(id) = server.id() {
kill(Pid::from_raw(id.cast_signed()), Signal::SIGINT)?;
}
timeout(Duration::from_secs(1), server.wait()).await??;
Ok(())
}
async fn is_alive(&self) -> bool {
let try_wait = self.server.lock().await.try_wait();
match try_wait {
Ok(Some(code)) => {
warn!(?code, "server has exited");
false
}
Ok(None) => true,
Err(err) => {
warn!(
?err,
"failed to check the status of server process, ignoring"
);
true
}
}
}
}
async fn watch_server(arbiter: Arbiter, server: Arc<Server>) -> Result<()> {
info!("starting server watcher");
loop {
tokio::select! {
() = sleep(Duration::from_secs(5)) => {
if !server.is_alive().await {
return Err(eyre!("server has exited unexpectedly"));
}
}
() = arbiter.shutdown() => {
server.shutdown().await?;
return Ok(());
}
}
}
}
pub(crate) async fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Server>> {
let arbiter = tasks.arbiter();
let server = Arc::new(Server::new().await?);
tasks
.build_task()
.name(&format!("{}::watch_server", module_path!()))
.spawn(watch_server(arbiter.clone(), Arc::clone(&server)))?;
Ok(server)
}
+1 -10
View File
@@ -343,14 +343,7 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
let router = healthcheck::build_router(Arc::clone(&workers)); let router = healthcheck::build_router(Arc::clone(&workers));
for addr in config::get().listen.http.iter().copied() { for addr in config::get().listen.http.iter().copied() {
ak_axum::server::start_plain( ak_axum::server::start_plain(tasks, "worker", router.clone(), addr)?;
tasks,
"worker",
router.clone(),
addr,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
)?;
} }
ak_axum::server::start_unix( ak_axum::server::start_unix(
@@ -358,8 +351,6 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
"worker", "worker",
router, router,
unix::net::SocketAddr::from_pathname(socket_path())?, unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
)?; )?;
} }
@@ -145,30 +145,24 @@ If you ever want to start over, use `make dev-reset`, which drops and restores t
## 5. Running authentik ## 5. Running authentik
Now that the backend has been set up and built, you can start authentik. In two different tabs in your terminal, run the following commands from the root of your installation directory: Now that the backend has been set up and built, you can start authentik. Run the following command from the root of your installation directory:
```shell ```shell
make run-server make run
```
```shell
make run-worker
``` ```
:::info :::info
The very first time a worker runs, it might need some time to clear the initial task queue. Adjust [`AUTHENTIK_WORKER__THREADS`](../../../install-config/configuration/#authentik_worker__threads) as required. The very first time authentik runs, it might need some time to clear the initial task queue. Adjust [`AUTHENTIK_WORKER__THREADS`](../../../install-config/configuration/#authentik_worker__threads) as required.
::: :::
Both processes need to run to get a fully functioning authentik development environment.
### Hot-reloading ### Hot-reloading
When `AUTHENTIK_DEBUG` is set to `true` (the default for the development environment), the authentik server automatically reloads whenever changes are made to the code. When `AUTHENTIK_DEBUG` is set to `true` (the default for the development environment), the authentik server automatically reloads whenever changes are made to the code.
For the authentik worker, install [watchexec](https://github.com/watchexec/watchexec), and run: Install [watchexec](https://github.com/watchexec/watchexec), and run:
```shell ```shell
make run-worker-watch make run-watch
``` ```
## 6. Build the frontend ## 6. Build the frontend