Compare commits
10 Commits
6bf847a24a
...
renovate/a
| Author | SHA1 | Date | |
|---|---|---|---|
| cff947857b | |||
|
63c29999bf
|
|||
|
a470122745
|
|||
|
d32d343695
|
|||
|
251d3922bf
|
|||
|
1b8924e7f6
|
|||
| f586d157b1 | |||
|
66080374b0
|
|||
|
92e435e080
|
|||
|
9510b8fc42
|
2
.drone.yml
Normal file
2
.drone.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
kind: template
|
||||
load: cuddle-rust-lib-plan.yaml
|
||||
@@ -1,34 +0,0 @@
|
||||
when:
|
||||
- event: [pull_request, tag]
|
||||
- event: push
|
||||
branch:
|
||||
- main
|
||||
|
||||
variables:
|
||||
- &rust_image 'rustlang/rust:nightly'
|
||||
|
||||
steps:
|
||||
build:
|
||||
image: *rust_image
|
||||
group: ci
|
||||
commands:
|
||||
- "cargo build"
|
||||
|
||||
test:
|
||||
image: *rust_image
|
||||
group: ci
|
||||
commands:
|
||||
- "cargo test"
|
||||
|
||||
lint:
|
||||
image: *rust_image
|
||||
group: ci
|
||||
commands:
|
||||
- "cargo clippy"
|
||||
|
||||
fmt:
|
||||
image: *rust_image
|
||||
group: ci
|
||||
commands:
|
||||
- "cargo fmt --all --check"
|
||||
|
||||
1469
Cargo.lock
generated
1469
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -13,10 +13,10 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
async-trait = { version = "0.1", features = [] }
|
||||
|
||||
axum = { git = "https://github.com/tokio-rs/axum", branch = "main", features = [
|
||||
axum = { version = "0.8.0", features = [
|
||||
"macros",
|
||||
] }
|
||||
axum-extra = { git = "https://github.com/tokio-rs/axum", branch = "main", features = [
|
||||
axum-extra = { version = "0.10.0", features = [
|
||||
"cookie",
|
||||
"cookie-private",
|
||||
"typed-header",
|
||||
@@ -28,17 +28,17 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1" }
|
||||
|
||||
uuid = {version = "1.5.0", features = ["v4"]}
|
||||
sqlx = { version = "0.7", features = [
|
||||
sqlx = { version = "0.8", features = [
|
||||
"runtime-tokio",
|
||||
"postgres",
|
||||
"migrate",
|
||||
] }
|
||||
|
||||
zitadel = { version = "3.4", features = ["axum"] }
|
||||
tower = "0.4"
|
||||
tower-http = { version = "0.4", features = ["cors", "trace"] }
|
||||
oauth2 = "4.4.2"
|
||||
openidconnect = "3.4"
|
||||
zitadel = { version = "5.0", features = ["axum"] }
|
||||
tower = "0.5"
|
||||
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
||||
oauth2 = "5.0.0"
|
||||
openidconnect = "4.0"
|
||||
|
||||
pretty_assertions = "1.4.0"
|
||||
sealed_test = "1.0.0"
|
||||
|
||||
@@ -9,14 +9,19 @@ use crate::{
|
||||
introspection::{IdToken, IntrospectionService},
|
||||
login::{auth_clap::AuthEngine, config::ConfigClap, AuthClap},
|
||||
oauth::{zitadel::ZitadelConfig, OAuth},
|
||||
session::{SessionService, User},
|
||||
session::{AppSession, SessionService, User},
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait Auth {
|
||||
async fn login(&self) -> anyhow::Result<Url>;
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)>;
|
||||
async fn login_token(&self, user: &str, password: &str) -> anyhow::Result<IdToken>;
|
||||
async fn login_authorized(&self, code: &str, state: &str) -> anyhow::Result<(HeaderMap, Url)>;
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
code: &str,
|
||||
state: &str,
|
||||
app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)>;
|
||||
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User>;
|
||||
}
|
||||
|
||||
@@ -84,15 +89,32 @@ pub struct ZitadelAuthService {
|
||||
config: ConfigClap,
|
||||
}
|
||||
pub static COOKIE_NAME: &str = "SESSION";
|
||||
pub static COOKIE_APP_SESSION_NAME: &str = "APP_SESSION";
|
||||
|
||||
#[async_trait]
|
||||
impl Auth for ZitadelAuthService {
|
||||
async fn login(&self) -> anyhow::Result<Url> {
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let mut headers = HeaderMap::new();
|
||||
if let Some(return_url) = return_url.clone() {
|
||||
let cookie_value = self.session.insert(AppSession { return_url }).await?;
|
||||
|
||||
let cookie = format!(
|
||||
"{}={}; SameSite=Lax; Path=/",
|
||||
COOKIE_APP_SESSION_NAME, cookie_value
|
||||
);
|
||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||
}
|
||||
|
||||
let authorize_url = self.oauth.authorize_url().await?;
|
||||
|
||||
Ok(authorize_url)
|
||||
Ok((headers, authorize_url))
|
||||
}
|
||||
async fn login_authorized(&self, code: &str, _state: &str) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
code: &str,
|
||||
_state: &str,
|
||||
app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let token = self.oauth.exchange(code).await?;
|
||||
let id_token = self.introspection.get_id_token(token.as_str()).await?;
|
||||
let cookie_value = self.session.insert_user("user", id_token).await?;
|
||||
@@ -102,9 +124,22 @@ impl Auth for ZitadelAuthService {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||
|
||||
let mut return_url = self.config.return_url.clone();
|
||||
if let Some(cookie) = app_session_cookie {
|
||||
if let Some(session) = self.session.get(&cookie).await? {
|
||||
if session.return_url.starts_with('/') {
|
||||
let mut url = Url::parse(&return_url)?;
|
||||
url.set_path(&session.return_url);
|
||||
return_url = url.to_string();
|
||||
} else {
|
||||
return_url = session.return_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
headers,
|
||||
Url::parse(&self.config.return_url)
|
||||
Url::parse(&return_url)
|
||||
.context("failed to parse login_authorized zitadel return url")?,
|
||||
))
|
||||
}
|
||||
@@ -126,7 +161,7 @@ pub struct NoopAuthService {
|
||||
|
||||
#[async_trait]
|
||||
impl Auth for NoopAuthService {
|
||||
async fn login(&self) -> anyhow::Result<Url> {
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let url = Url::parse(&format!(
|
||||
"{}/auth/authorized?code=noop&state=noop",
|
||||
self.config
|
||||
@@ -136,12 +171,13 @@ impl Auth for NoopAuthService {
|
||||
.unwrap()
|
||||
))
|
||||
.unwrap();
|
||||
Ok(url)
|
||||
Ok((HeaderMap::new(), url))
|
||||
}
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
_code: &str,
|
||||
_state: &str,
|
||||
_app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let cookie_value = self
|
||||
.session
|
||||
|
||||
@@ -2,23 +2,23 @@ use std::fmt::Display;
|
||||
|
||||
use axum::extract::{FromRef, FromRequestParts, Query, State};
|
||||
use axum::http::request::Parts;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{ErrorResponse, IntoResponse, Redirect};
|
||||
use axum::http::{HeaderMap, StatusCode, Uri};
|
||||
use axum::response::{ErrorResponse, IntoResponse, Redirect, Response};
|
||||
use axum::routing::get;
|
||||
use axum::{async_trait, Json, RequestPartsExt, Router};
|
||||
|
||||
use axum_extra::extract::CookieJar;
|
||||
use axum_extra::headers::authorization::Basic;
|
||||
use axum_extra::headers::{Authorization, Cookie};
|
||||
use axum_extra::TypedHeader;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::auth::AuthService;
|
||||
use crate::auth::{AuthService, COOKIE_APP_SESSION_NAME};
|
||||
use crate::session::User;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ZitadelAuthParams {
|
||||
#[allow(dead_code)]
|
||||
return_url: Option<String>,
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ where
|
||||
pub async fn zitadel_auth(
|
||||
State(auth_service): State<AuthService>,
|
||||
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||
let url = auth_service.login().await.into_response()?;
|
||||
let (headers, url) = auth_service.login(None).await.into_response()?;
|
||||
|
||||
Ok(Redirect::to(url.as_ref()))
|
||||
Ok((headers, Redirect::to(url.as_ref())))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -66,9 +66,14 @@ pub struct AuthRequest {
|
||||
pub async fn login_authorized(
|
||||
Query(query): Query<AuthRequest>,
|
||||
State(auth_service): State<AuthService>,
|
||||
cookie_jar: CookieJar,
|
||||
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||
let cookie_value = cookie_jar
|
||||
.get(COOKIE_APP_SESSION_NAME)
|
||||
.map(|c| c.value().to_string());
|
||||
|
||||
let (headers, url) = auth_service
|
||||
.login_authorized(&query.code, &query.state)
|
||||
.login_authorized(&query.code, &query.state, cookie_value)
|
||||
.await
|
||||
.into_response()?;
|
||||
|
||||
@@ -92,13 +97,21 @@ pub struct UserFromSession {
|
||||
|
||||
pub static COOKIE_NAME: &str = "SESSION";
|
||||
|
||||
pub struct AuthRedirect((HeaderMap, String));
|
||||
|
||||
impl IntoResponse for AuthRedirect {
|
||||
fn into_response(self) -> Response {
|
||||
(self.0 .0, Redirect::temporary(&self.0 .1.as_str())).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for UserFromSession
|
||||
where
|
||||
AuthService: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = (StatusCode, &'static str);
|
||||
type Rejection = AuthRedirect;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let auth_service = AuthService::from_ref(state);
|
||||
@@ -109,16 +122,21 @@ where
|
||||
let basic: Option<TypedHeader<Authorization<Basic>>> = parts.extract().await.unwrap();
|
||||
|
||||
if let Some(basic) = basic {
|
||||
let token = auth_service
|
||||
let token = match auth_service
|
||||
.login_token(basic.username(), basic.password())
|
||||
.await
|
||||
.into_response()
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"could not get token from basic",
|
||||
)
|
||||
})?;
|
||||
{
|
||||
Ok(login) => login,
|
||||
Err(e) => {
|
||||
tracing::info!("did not find a basic login token, will trigger login");
|
||||
let (headers, url) = auth_service
|
||||
.login(Some(parts.uri.to_string()))
|
||||
.await
|
||||
.expect("to be able to request login");
|
||||
return Err(AuthRedirect((headers, url.to_string())));
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(UserFromSession {
|
||||
user: User {
|
||||
@@ -129,24 +147,32 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
return Err(anyhow::anyhow!("No session was found"))
|
||||
.into_response()
|
||||
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "did not find a cookie"))?;
|
||||
tracing::info!("did not find a cookie, will trigger login");
|
||||
let (headers, url) = auth_service
|
||||
.login(Some(parts.uri.to_string()))
|
||||
.await
|
||||
.expect("to be able to request login");
|
||||
return Err(AuthRedirect((headers, url.to_string())));
|
||||
}
|
||||
|
||||
let session_cookie = session_cookie.unwrap();
|
||||
|
||||
// continue to decode the session cookie
|
||||
let user = auth_service
|
||||
let user = match auth_service
|
||||
.get_user_from_session(session_cookie)
|
||||
.await
|
||||
.into_response()
|
||||
.map_err(|_| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"failed to decode session cookie",
|
||||
)
|
||||
})?;
|
||||
{
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
tracing::info!("could not get user from session, will trigger login");
|
||||
let (headers, url) = auth_service
|
||||
.login(Some(parts.uri.to_string()))
|
||||
.await
|
||||
.expect("to be able to request login");
|
||||
return Err(AuthRedirect((headers, url.to_string())));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UserFromSession { user })
|
||||
}
|
||||
|
||||
@@ -105,14 +105,15 @@ impl OAuthClient for ZitadelOAuthClient {
|
||||
Ok(())
|
||||
}
|
||||
async fn authorize_url(&self) -> anyhow::Result<Url> {
|
||||
let (auth_url, _csrf_token) = self
|
||||
let req = self
|
||||
.client
|
||||
.authorize_url(CsrfToken::new_random)
|
||||
.add_scope(Scope::new("identify".to_string()))
|
||||
.add_scope(Scope::new("openid".to_string()))
|
||||
.add_scope(Scope::new("email".to_string()))
|
||||
.add_scope(Scope::new("profile".to_string()))
|
||||
.url();
|
||||
.add_scope(Scope::new("profile".to_string()));
|
||||
|
||||
let (auth_url, _csrf_token) = req.url();
|
||||
|
||||
Ok(auth_url)
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ pub struct PostgresqlSessionClap {
|
||||
|
||||
#[async_trait]
|
||||
pub trait Session {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String>;
|
||||
async fn insert_user(&self, id: &str, id_token: IdToken) -> anyhow::Result<String>;
|
||||
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>>;
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>>;
|
||||
}
|
||||
|
||||
pub struct SessionService(Arc<dyn Session + Send + Sync + 'static>);
|
||||
|
||||
impl SessionService {
|
||||
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
||||
match config.session_backend {
|
||||
@@ -77,8 +78,26 @@ pub struct User {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AppSession {
|
||||
pub return_url: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Session for PostgresSessionService {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String> {
|
||||
let mut session = AxumSession::new();
|
||||
session.insert("app_session", app_session)?;
|
||||
|
||||
let cookie = self
|
||||
.store
|
||||
.store_session(session)
|
||||
.await?
|
||||
.ok_or(anyhow::anyhow!("failed to store app session"))?;
|
||||
|
||||
Ok(cookie)
|
||||
}
|
||||
|
||||
async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
|
||||
let mut session = AxumSession::new();
|
||||
session.insert(
|
||||
@@ -117,6 +136,18 @@ impl Session for PostgresSessionService {
|
||||
Err(anyhow::anyhow!("No session found for cookie"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>> {
|
||||
let Some(session) = self.store.load_session(cookie.to_string()).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(session) = session.get::<AppSession>("app_session") else {
|
||||
anyhow::bail!("failed to deserialize app_session from cookie");
|
||||
};
|
||||
|
||||
Ok(Some(session))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -126,6 +157,9 @@ pub struct InMemorySessionService {
|
||||
|
||||
#[async_trait]
|
||||
impl Session for InMemorySessionService {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String> {
|
||||
todo!()
|
||||
}
|
||||
async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
|
||||
let user = User {
|
||||
id: id_token.sub,
|
||||
@@ -145,4 +179,7 @@ impl Session for InMemorySessionService {
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,9 @@ base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git"
|
||||
vars:
|
||||
service: "nefarious-login"
|
||||
registry: kasperhermansen
|
||||
|
||||
scripts:
|
||||
local_up:
|
||||
type: shell
|
||||
local_down:
|
||||
type: shell
|
||||
|
||||
16
examples/custom_redirect/Cargo.toml
Normal file
16
examples/custom_redirect/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "custom_redirect"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nefarious-login.workspace = true
|
||||
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
|
||||
tracing-subscriber.workspace = true
|
||||
94
examples/custom_redirect/src/main.rs
Normal file
94
examples/custom_redirect/src/main.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use nefarious_login::{
|
||||
auth::AuthService,
|
||||
axum::{AuthController, UserFromSession},
|
||||
login::{
|
||||
auth_clap::{AuthEngine, ZitadelClap},
|
||||
config::ConfigClap,
|
||||
AuthClap,
|
||||
},
|
||||
session::{PostgresqlSessionClap, SessionBackend},
|
||||
};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
auth: AuthService,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let auth = AuthClap {
|
||||
engine: AuthEngine::Zitadel,
|
||||
session_backend: SessionBackend::Postgresql,
|
||||
zitadel: ZitadelClap {
|
||||
authority_url: Some("https://personal-wxuujs.zitadel.cloud".into()),
|
||||
client_id: Some("237412977047895154@nefarious-test".into()),
|
||||
client_secret: Some(
|
||||
"rWwDi8gjNOyuMFKoOjNSlhjcVZ1B25wDh6HsDL27f0g2Hb0xGbvEf0WXFY2akOlL".into(),
|
||||
),
|
||||
redirect_url: Some("http://localhost:3001/auth/authorized".into()),
|
||||
},
|
||||
session: nefarious_login::session::SessionClap {
|
||||
postgresql: PostgresqlSessionClap {
|
||||
conn: Some("postgres://nefarious-test:somenotverysecurepassword@localhost:5432/nefarious-test".into()),
|
||||
},
|
||||
},
|
||||
config: ConfigClap { return_url: "http://localhost:3001/authed".into() } // this normally has /authed
|
||||
};
|
||||
|
||||
let auth_service = AuthService::new(&auth).await?;
|
||||
|
||||
let state = AppState {
|
||||
auth: auth_service.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/unauthed", get(unauthed))
|
||||
.route("/authed", get(authed))
|
||||
.route("/login", get(login))
|
||||
.with_state(state)
|
||||
.nest("/auth", AuthController::new_router(auth_service).await?);
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{}:{}", "127.0.0.1", "3000"))?;
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for AuthService {
|
||||
fn from_ref(input: &AppState) -> Self {
|
||||
input.auth.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn login(State(auth_service): State<AuthService>) -> impl IntoResponse {
|
||||
let (headers, url) = auth_service.login(Some("/authed".into())).await.unwrap();
|
||||
|
||||
(headers, Redirect::to(url.as_ref()))
|
||||
}
|
||||
|
||||
async fn unauthed() -> String {
|
||||
"Hello Unauthorized User".into()
|
||||
}
|
||||
|
||||
#[axum::debug_handler()]
|
||||
async fn authed(
|
||||
user: UserFromSession,
|
||||
State(_auth_service): State<AuthService>,
|
||||
) -> impl IntoResponse {
|
||||
format!("Hello authorized user: {:?}", user.user.id)
|
||||
}
|
||||
7
scripts/local_down.sh
Executable file
7
scripts/local_down.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cuddle render_template --template-file $TMP/docker-compose.local_up.yml.tmpl --dest $TMP/docker-compose.local_up.yml
|
||||
|
||||
docker compose -f $TMP/docker-compose.local_up.yml down -v
|
||||
9
scripts/local_up.sh
Executable file
9
scripts/local_up.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cuddle render_template --template-file $TMP/docker-compose.local_up.yml.tmpl --dest $TMP/docker-compose.local_up.yml
|
||||
|
||||
docker compose -f $TMP/docker-compose.local_up.yml up -d --remove-orphans --build
|
||||
|
||||
sleep 3
|
||||
7
templates/docker-compose.local_up.dockerignore
Normal file
7
templates/docker-compose.local_up.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
target/
|
||||
.git/
|
||||
.cuddle/
|
||||
scripts/
|
||||
cuddle.yaml
|
||||
local.sh
|
||||
README.md
|
||||
17
templates/docker-compose.local_up.yml.tmpl
Normal file
17
templates/docker-compose.local_up.yml.tmpl
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: bitnami/postgresql:16
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRESQL_USERNAME=nefarious-test
|
||||
- POSTGRESQL_DATABASE=nefarious-test
|
||||
- POSTGRESQL_PASSWORD=somenotverysecurepassword
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
Reference in New Issue
Block a user