Compare commits

..

9 commits

Author SHA1 Message Date
ca6bae8463
fix: CI: uses '%' instead of '/' in sed
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-12 21:59:31 +05:30
ad0c7ae94d
feat: CI: setup nginx to load custom configuration files and create dummy website for testing 2022-12-12 21:59:31 +05:30
e7068cfc7c
feat: test if vhost is loaded into nginx 2022-12-12 21:59:30 +05:30
62278abede
feat: create and delete sites on nginx with tests 2022-12-12 21:59:30 +05:30
1e5a3d57b5
feat: create site templates 2022-12-12 21:59:30 +05:30
e9237d3586
feat: include hostname while sending config event
hostname is autogenerated by librepages, config files, location in file
system, etc. are identified by hostname. So it makes sense to send
hostname, along with the custom domain names that the customer provides
2022-12-12 21:59:30 +05:30
efdff0bc26
fix: publish docker images only on pushes to master branch 2022-12-12 21:59:30 +05:30
2401d40047
feat: CI: install nginx to run nginx conductor integration tests 2022-12-12 21:59:30 +05:30
bfaf077c02
feat: implement LibConductor::health for nginx conductor 2022-12-12 21:59:30 +05:30
24 changed files with 1812 additions and 705 deletions

View file

@ -1,19 +1,23 @@
steps:
test:
pipeline:
backend:
image: rust
# environment:
# - DATABASE_URL=postgres://postgres:password@database:5432/postgres
commands:
# - make migrate
- apt update
- apt-get install -y --no-install-recommends nginx sudo
- mkdir -p /etc/librepages/nginx/sites-available
- mkdir -p /etc/librepages/nginx/sites-enabled/
- sed -i "s%include \/etc\/nginx\/sites-enabled%include \/etc\/librepages\/nginx\/sites-enabled%" /etc/nginx/nginx.conf
- mkdir /var/www/website/ && echo "Hello Librepages" > /var/www/website/index.html
# nginx_le_bind runs this command.
# Testing beforehand to ensure it is setup properly
- sudo nginx -t
- make
- make test
- make release
publish_bins:
image: rust
when:
event: [push, tag, deployment]
branch: master
commands:
- apt update
- apt-get -y --no-install-recommends install gpg tar curl wget
@ -24,7 +28,7 @@ steps:
publish:
image: plugins/docker
when:
event: [push, tag, deployment]
event: push
branch: master
settings:
username: realaravinth
@ -32,9 +36,3 @@ steps:
from_secret: DOCKER_TOKEN
repo: realaravinth/librepages-conductor
tags: latest
#services:
# database:
# image: postgres
# environment:
# - POSTGRES_PASSWORD=password

661
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,23 +12,22 @@ build = "build.rs"
[dependencies]
actix-web = "4"
actix-web-prom = "0.8.0"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
lazy_static = "1.4.0"
log = "0.4.17"
pretty_env_logger = "0.5.0"
pretty_env_logger = "0.4.0"
serde = { version = "1", features=["derive"]}
actix-web-codegen-const-routes = { version = "0.1.0", tag = "0.1.0", git = "https://github.com/realaravinth/actix-web-codegen-const-routes" }
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
derive_builder = "0.20.0"
config = "0.14"
derive_builder = "0.11.2"
config = "0.13"
derive_more = "0.99.17"
url = { version = "2.2.2", features = ["serde"]}
serde_json = { version ="1", features = ["raw_value"]}
clap = { vesrion = "3.2.20", features = ["derive"]}
actix-web-httpauth = "0.8.0"
mime_guess = "2.0.4"
rust-embed = "8.0.0"
rust-embed = "6.4.2"
[dependencies.libconductor]
path = "./env/libconductor"
@ -42,4 +41,4 @@ sqlx = { version = "0.6.1", features = [ "runtime-actix-rustls", "postgres", "ti
[dev-dependencies]
actix-rt = "2.7.0"
base64 = "0.22.0"
base64 = "0.13.0"

View file

@ -19,7 +19,7 @@ RUN cargo --version
#RUN make cache-bust
RUN cargo build --release
FROM debian:bookworm as conductor
FROM debian:bullseye as conductor
LABEL org.opencontainers.image.source https://git.batsense.net/librepages/conductor
RUN apt-get update && apt-get install -y ca-certificates
RUN useradd -ms /bin/bash -u 1001 conductor

View file

@ -3,7 +3,8 @@ source_code = "https://git.batsense.net/librepages/conductor"
conductor = "dummy"
[creds]
token="longrandomlygeneratedpassword"
username = "librepages_api"
password="longrandomlygeneratedpassword"
[server]
# Please set a unique value, your mCaptcha instance's security depends on this being

View file

@ -1,24 +0,0 @@
[Unit]
Description=LibrePages Conductor: Easiest way to deploy websites. Conductor component
[Service]
Type=simple
User=root
ExecStart=/usr/bin/conductor serve
Restart=on-failure
RestartSec=1
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
NoNewPrivileges=true
Environment="RUST_LOG=info"
[Unit]
Wants=network-online.target
Wants=network-online.target
Requires=postgresql.service
After=syslog.target
[Install]
WantedBy=multi-user.target

195
env/dummy_conductor/Cargo.lock generated vendored
View file

@ -2,58 +2,22 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "async-trait"
version = "0.1.80"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn",
]
[[package]]
name = "backtrace"
version = "0.3.72"
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "cc"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "dummy_conductor"
@ -66,12 +30,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -89,9 +47,9 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "libconductor"
@ -111,21 +69,6 @@ dependencies = [
"serde",
]
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "miniz_oxide"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
dependencies = [
"adler",
]
[[package]]
name = "num_cpus"
version = "1.14.0"
@ -136,45 +79,30 @@ dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"memchr",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.85"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "ryu"
version = "1.0.11"
@ -183,29 +111,29 @@ checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"ryu",
@ -223,38 +151,28 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.38.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"backtrace",
"autocfg",
"num_cpus",
"pin-project-lite",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.3.0"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn",
]
[[package]]
@ -262,3 +180,60 @@ name = "unicode-ident"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"

28
env/libconductor/Cargo.lock generated vendored
View file

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "async-trait"
version = "0.1.80"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
dependencies = [
"proc-macro2",
"quote",
@ -39,18 +39,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.85"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
@ -63,18 +63,18 @@ checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
@ -83,9 +83,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"ryu",
@ -94,9 +94,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.66"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",

View file

@ -30,6 +30,7 @@ pub enum EventType {
},
Config {
hostname: String,
data: LibConfig,
},
}

1
env/nginx_bind_le/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

1118
env/nginx_bind_le/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

24
env/nginx_bind_le/Cargo.toml vendored Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "nginx_bind_le"
version = "0.1.0"
edition = "2021"
include = ["/templates"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1", features=["derive"]}
serde_json = { version ="1", features = ["raw_value"]}
async-trait = "0.1.57"
tokio = { version = "1.23.0", features = ["process", "fs", "io-util"] }
tera = "1.17.1"
rust-embed = "6.4.2"
lazy_static = "1.4.0"
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
[dependencies.libconductor]
path = "../libconductor"
[dev-dependencies]
tokio = { version = "1.23.0", features = ["rt-multi-thread", "macros", "rt"] }

102
env/nginx_bind_le/src/lib.rs vendored Normal file
View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use async_trait::async_trait;
use tokio::process::Command;
use libconductor::*;
mod nginx;
mod templates;
use nginx::Nginx;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NginxBindLEConductor;
const CONDUCTOR_NAME: &str = "NGINX_BIND_LE_CONDUCTOR";
#[async_trait]
impl Conductor for NginxBindLEConductor {
async fn process(&self, event: EventType) {
match event {
EventType::NewSite {
hostname,
path,
branch: _branch,
} => {
Nginx::new_site(&hostname, &path, None).await.unwrap();
}
EventType::Config { hostname, data } => {
unimplemented!();
// Nginx::new_site(&hostname, &path, Some(data)).await.unwrap();
}
EventType::DeleteSite { hostname } => {
Nginx::rm_site(&hostname).await.unwrap();
}
};
}
fn name(&self) -> &'static str {
CONDUCTOR_NAME
}
async fn health(&self) -> bool {
nginx::Nginx::env_exists() && nginx::Nginx::status().await
}
}
#[cfg(test)]
mod tests {
use std::process::Stdio;
use super::*;
#[tokio::test]
async fn all_good() {
const HOSTNAME: &str = "lab.batsense.net";
let c = NginxBindLEConductor {};
assert_eq!(c.name(), CONDUCTOR_NAME);
assert!(c.health().await);
if Nginx::site_exists(HOSTNAME) {
c.process(EventType::DeleteSite {
hostname: HOSTNAME.into(),
})
.await;
}
c.process(EventType::NewSite {
hostname: HOSTNAME.into(),
branch: "librepages".into(),
path: "/var/www/website/".into(),
})
.await;
let out = tokio::process::Command::new("sudo")
.arg("nginx")
.arg("-T")
.stdout(Stdio::piped())
.output()
.await
.unwrap();
let expected = format!("server_name {HOSTNAME}");
let out = String::from_utf8(out.stdout).unwrap();
assert!(out.contains(&expected));
c.process(EventType::DeleteSite {
hostname: HOSTNAME.into(),
})
.await;
}
}

132
env/nginx_bind_le/src/nginx.rs vendored Normal file
View file

@ -0,0 +1,132 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::cell::RefCell;
use std::error::Error;
use std::path::{Path, PathBuf};
use tera::*;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use crate::templates::*;
pub struct Nginx;
impl Nginx {
pub async fn reload() -> MyResult<()> {
Command::new("sudo")
.arg("nginx")
.arg("-s")
.arg("reload")
.spawn()?
.wait()
.await?;
Ok(())
}
pub async fn status() -> bool {
async fn run_async_cmd(cmd: &mut Command) -> bool {
if let Ok(mut child) = cmd.spawn() {
if let Ok(res) = child.wait().await {
return res.success();
}
}
false
}
run_async_cmd(Command::new("sudo").arg("nginx").arg("-t")).await
}
pub async fn new_site(
hostname: &str,
path: &str,
config: Option<libconfig::Config>,
) -> MyResult<()> {
let config = CreateSite::new(hostname, path, config);
let contents = config.render();
let staging = Self::get_staging(hostname);
let prod = Self::get_prod(hostname);
let mut file = fs::File::create(&staging).await?;
file.write_all(contents.as_bytes()).await?;
file.sync_all().await?;
fs::symlink(&staging, &prod).await?;
Self::reload().await
}
fn get_staging(hostname: &str) -> PathBuf {
Path::new(NGINX_STAGING_CONFIG_PATH).join(hostname)
}
fn get_prod(hostname: &str) -> PathBuf {
Path::new(NGINX_PRODUCTION_CONFIG_PATH).join(hostname)
}
pub fn site_exists(hostname: &str) -> bool {
Self::get_prod(hostname).exists()
}
pub async fn rm_site(hostname: &str) -> MyResult<()> {
let staging = Self::get_staging(hostname);
let prod = Self::get_prod(hostname);
fs::remove_file(&prod).await?;
fs::remove_file(&staging).await?;
Self::reload().await
}
pub fn env_exists() -> bool {
let prod = Path::new(NGINX_PRODUCTION_CONFIG_PATH);
let staging = Path::new(NGINX_STAGING_CONFIG_PATH);
prod.exists() && prod.is_dir() && staging.exists() && staging.is_dir()
}
}
pub struct CreateSite {
ctx: RefCell<Context>,
}
pub const CREATE_SITE: TemplateFile = TemplateFile::new("create_site", "nginx/create-site.j2");
pub const CREATE_SITE_FRAGMENT: TemplateFile =
TemplateFile::new("new_site_frag", "nginx/_new_site.fragement.j2");
pub const HOSTNAME_KEY: &str = "hostname";
pub const DOMAINS_KEY: &str = "domains";
pub const PATH_KEY: &str = "path";
pub const REDIRECTS_KEY: &str = "redirects";
pub const NGINX_STAGING_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-available/";
pub const NGINX_PRODUCTION_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-enabled/";
type MyResult<T> = std::result::Result<T, Box<dyn Error>>;
impl CreateSite {
fn new(hostname: &str, path: &str, config: Option<libconfig::Config>) -> Self {
let ctx = RefCell::new(context());
ctx.borrow_mut().insert(HOSTNAME_KEY, hostname);
ctx.borrow_mut().insert(PATH_KEY, path);
if let Some(config) = config {
ctx.borrow_mut().insert(REDIRECTS_KEY, &config.redirects);
ctx.borrow_mut().insert(DOMAINS_KEY, &config.domains);
}
Self { ctx }
}
fn render(&self) -> String {
TEMPLATES
.render(CREATE_SITE.name, &self.ctx.borrow())
.unwrap()
}
}

73
env/nginx_bind_le/src/templates.rs vendored Normal file
View file

@ -0,0 +1,73 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use lazy_static::lazy_static;
use rust_embed::RustEmbed;
use tera::*;
pub const PAYLOAD_KEY: &str = "payload";
lazy_static! {
pub static ref TEMPLATES: Tera = {
let mut tera = Tera::default();
for template in [crate::nginx::CREATE_SITE, crate::nginx::CREATE_SITE_FRAGMENT].iter() {
template.register(&mut tera).expect(template.name);
}
// tera.autoescape_on(vec![".html", ".sql"]);
tera
};
}
#[derive(RustEmbed)]
#[folder = "templates/"]
pub struct Templates;
impl Templates {
pub fn get_template(t: &TemplateFile) -> Option<String> {
match Self::get(t.path) {
Some(file) => Some(String::from_utf8_lossy(&file.data).into_owned()),
None => None,
}
}
}
pub fn context() -> Context {
let mut ctx = Context::new();
ctx
}
pub struct TemplateFile {
pub name: &'static str,
pub path: &'static str,
}
impl TemplateFile {
pub const fn new(name: &'static str, path: &'static str) -> Self {
Self { name, path }
}
pub fn register(&self, t: &mut Tera) -> std::result::Result<(), tera::Error> {
t.add_raw_template(self.name, &Templates::get_template(self).expect(self.name))
}
#[cfg(test)]
#[allow(dead_code)]
pub fn register_from_file(&self, t: &mut Tera) -> std::result::Result<(), tera::Error> {
use std::path::Path;
t.add_template_file(Path::new("templates/").join(self.path), Some(self.name))
}
}

View file

@ -0,0 +1 @@
sudo certbot --nginx -d {{ hostname }}

View file

@ -0,0 +1,29 @@
server {
# serve website on port 80
listen [::]:80;
listen 80;
# write error logs to file
error_log /var/log/nginx/{{ hostname }}.error.log;
# write access logs to file
access_log /var/log/nginx/{{ hostname }}.access.log;
# serve only on this domain:
server_name {{ hostname }};
# use files from this directory
root {{ path }};
# remove .html from URL; it is cleaner this way
rewrite ^(/.*)\.html(\?.*)?$ $1$2 permanent;
{% if redirects %}
{% for redirect in redirects %}
rewrite ^/{{redirect.from}}$ /{{ redirect.to }} redirect;
{% endfor %}
{% endif %}
# when a request is received, try the index.html in the directory
# or $uri.html
try_files $uri/index.html $uri.html $uri/ $uri =404;
}

View file

@ -0,0 +1,7 @@
{% include "new_site_frag" %}
{% if domains %}
{% for hostname in domains %}
{% include "new_site_frag" %}
{% endfor %}
{% endif %}

View file

@ -1,21 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard"
],
"labels": [
"renovate-bot"
],
"prHourlyLimit": 0,
"timezone": "Asia/kolkata",
"prCreation": "immediate",
"vulnerabilityAlerts": {
"enabled": true,
"labels": [
"renovate-bot",
"renovate-security",
"security"
]
}
}

View file

@ -38,8 +38,6 @@ DOCKER_IMG="realaravinth/$NAME:$3"
get_bin(){
cp target/release/conductor $TARGET_DIR
cp -r config/ $TARGET_DIR
cp -r contrib/ $TARGET_DIR
}
copy() {

View file

@ -17,7 +17,7 @@
use actix_web::dev::ServiceRequest;
use actix_web::web;
use actix_web::Error;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use actix_web_httpauth::extractors::basic::BasicAuth;
use crate::errors::*;
use crate::AppCtx;
@ -26,13 +26,14 @@ use crate::SETTINGS;
pub mod meta;
pub mod webhook;
pub async fn bearerauth(
pub async fn httpauth(
req: ServiceRequest,
credentials: BearerAuth,
credentials: BasicAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
let _ctx: &AppCtx = req.app_data().unwrap();
let token = credentials.token();
if SETTINGS.authenticate(token) {
let username = credentials.user_id();
let password = credentials.password().unwrap();
if SETTINGS.authenticate(username, password) {
Ok(req)
} else {
let e = Error::from(ServiceError::Unauthorized);

View file

@ -24,7 +24,7 @@ use crate::errors::*;
use crate::AppCtx;
use crate::*;
use super::bearerauth;
use super::httpauth;
pub mod routes {
use super::*;
@ -47,7 +47,7 @@ pub fn services(cfg: &mut web::ServiceConfig) {
#[actix_web_codegen_const_routes::post(
path = "API_V1_ROUTES.webhook.post_event",
wrap = "HttpAuthentication::bearer(bearerauth)"
wrap = "HttpAuthentication::basic(httpauth)"
)]
async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult<impl Responder> {
ctx.conductor.process(payload.into_inner()).await;
@ -71,7 +71,10 @@ pub mod tests {
.await;
let creds = settings.creds.clone();
let auth = format!("Bearer {}", creds.token,);
let auth = format!(
"Basic {}",
base64::encode(format!("{}:{}", creds.username.clone(), creds.password))
);
let msg = EventType::NewSite {
hostname: "demo.librepages.org".into(),

View file

@ -19,7 +19,6 @@ use std::env;
use actix_web::http::StatusCode;
use actix_web::web::JsonConfig;
use actix_web::{error::InternalError, middleware, App, HttpServer};
use actix_web_prom::PrometheusMetricsBuilder;
use clap::{Parser, Subcommand};
use log::info;
@ -113,11 +112,6 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
let ip = settings.server.get_ip();
println!("Starting server on: http://{ip}");
let prometheus = PrometheusMetricsBuilder::new("api")
.endpoint("/metrics")
.build()
.unwrap();
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
@ -130,7 +124,6 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
middleware::TrailingSlash::Trim,
))
.app_data(get_json_err())
.wrap(prometheus.clone())
.configure(routes::services)
})
.bind(ip)?

View file

@ -54,7 +54,8 @@ pub enum ConductorType {
#[derive(Debug, Clone, Deserialize)]
pub struct Creds {
pub token: String,
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Deserialize)]
@ -68,15 +69,15 @@ pub struct Settings {
#[cfg(not(tarpaulin_include))]
impl Settings {
pub fn authenticate(&self, token: &str) -> bool {
self.creds.token == token
pub fn authenticate(&self, username: &str, password: &str) -> bool {
self.creds.username == username && self.creds.password == password
}
pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::builder();
const CURRENT_DIR: &str = "./config/config.toml";
const ETC: &str = "/etc/librepages/conductor/config.toml";
const ETC: &str = "/etc/lpconductor/config.toml";
if let Ok(path) = env::var("LPCONDUCTOR_CONFIG") {
s = s.add_source(File::with_name(&path));
@ -137,13 +138,6 @@ fn set_separator_field(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<Defa
&format!("{PREFIX}{SEPARATOR}SERVER{SEPARATOR}PROXY_HAS_TLS"),
"server.proxy_has_tls",
);
s = from_env(
s,
&format!("{PREFIX}{SEPARATOR}CREDS{SEPARATOR}TOKEN"),
"creds.token",
);
s
}
@ -154,13 +148,16 @@ mod tests {
#[test]
fn creds_works() {
let settings = Settings::new().unwrap();
let creds = settings.creds.clone();
let mut creds = settings.creds.clone();
assert!(settings.authenticate(&creds.token));
assert!(settings.authenticate(&creds.username, &creds.password));
creds.username = "noexist".into();
assert!(!settings.authenticate(&creds.username, &creds.password));
let mut creds = settings.creds.clone();
creds.token = "noexist".into();
assert!(!settings.authenticate(&creds.token))
creds.password = "noexist".into();
assert!(!settings.authenticate(&creds.username, &creds.password));
}
}