+6
-16
@@ -7,6 +7,7 @@ extern crate array_tool;
|
||||
extern crate base64;
|
||||
extern crate bcrypt;
|
||||
extern crate chrono;
|
||||
extern crate colored;
|
||||
extern crate comrak;
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
@@ -26,6 +27,7 @@ extern crate reqwest;
|
||||
extern crate rocket;
|
||||
extern crate rocket_contrib;
|
||||
extern crate rocket_i18n;
|
||||
extern crate rpassword;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
@@ -35,18 +37,17 @@ extern crate tera;
|
||||
extern crate url;
|
||||
extern crate webfinger;
|
||||
|
||||
use diesel::{pg::PgConnection, r2d2::{ConnectionManager, Pool}};
|
||||
use dotenv::dotenv;
|
||||
use rocket_contrib::Template;
|
||||
use std::env;
|
||||
|
||||
mod activity_pub;
|
||||
mod db_conn;
|
||||
mod models;
|
||||
mod safe_string;
|
||||
mod schema;
|
||||
mod setup;
|
||||
mod routes;
|
||||
mod utils;
|
||||
mod safe_string;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BASE_URL: String = env::var("BASE_URL")
|
||||
@@ -56,17 +57,8 @@ lazy_static! {
|
||||
.unwrap_or(format!("postgres://plume:plume@localhost/{}", env::var("DB_NAME").unwrap_or(String::from("plume"))));
|
||||
}
|
||||
|
||||
type PgPool = Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
/// Initializes a database pool.
|
||||
fn init_pool() -> PgPool {
|
||||
dotenv().ok();
|
||||
|
||||
let manager = ConnectionManager::<PgConnection>::new(DB_URL.as_str());
|
||||
Pool::new(manager).expect("DB pool error")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let pool = setup::check();
|
||||
rocket::ignite()
|
||||
.mount("/", routes![
|
||||
routes::blogs::details,
|
||||
@@ -81,8 +73,6 @@ fn main() {
|
||||
routes::comments::create,
|
||||
|
||||
routes::instance::index,
|
||||
routes::instance::configure,
|
||||
routes::instance::post_config,
|
||||
routes::instance::shared_inbox,
|
||||
routes::instance::nodeinfo,
|
||||
|
||||
@@ -133,7 +123,7 @@ fn main() {
|
||||
routes::errors::not_found,
|
||||
routes::errors::server_error
|
||||
])
|
||||
.manage(init_pool())
|
||||
.manage(pool)
|
||||
.attach(Template::custom(|engines| {
|
||||
rocket_i18n::tera(&mut engines.tera);
|
||||
}))
|
||||
|
||||
+6
-6
@@ -484,16 +484,16 @@ impl Signer for User {
|
||||
impl NewUser {
|
||||
/// Creates a new local user
|
||||
pub fn new_local(
|
||||
conn: &PgConnection,
|
||||
username: String,
|
||||
display_name: String,
|
||||
is_admin: bool,
|
||||
summary: String,
|
||||
email: String,
|
||||
password: String,
|
||||
instance_id: i32
|
||||
) -> NewUser {
|
||||
password: String
|
||||
) -> User {
|
||||
let (pub_key, priv_key) = gen_keypair();
|
||||
NewUser {
|
||||
User::insert(conn, NewUser {
|
||||
username: username,
|
||||
display_name: display_name,
|
||||
outbox_url: String::from(""),
|
||||
@@ -502,11 +502,11 @@ impl NewUser {
|
||||
summary: SafeString::new(&summary),
|
||||
email: Some(email),
|
||||
hashed_password: Some(password),
|
||||
instance_id: instance_id,
|
||||
instance_id: Instance::local_id(conn),
|
||||
ap_url: String::from(""),
|
||||
public_key: String::from_utf8(pub_key).unwrap(),
|
||||
private_key: Some(String::from_utf8(priv_key).unwrap()),
|
||||
shared_inbox_url: None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use gettextrs::gettext;
|
||||
use rocket::{request::Form, response::Redirect};
|
||||
use rocket_contrib::{Json, Template};
|
||||
use serde_json;
|
||||
|
||||
use BASE_URL;
|
||||
use activity_pub::inbox::Inbox;
|
||||
use db_conn::DbConn;
|
||||
use models::{
|
||||
@@ -33,31 +31,6 @@ fn index(conn: DbConn, user: Option<User>) -> Template {
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/configure")]
|
||||
fn configure() -> Template {
|
||||
Template::render("instance/configure", json!({}))
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct NewInstanceForm {
|
||||
name: String
|
||||
}
|
||||
|
||||
#[post("/configure", data = "<data>")]
|
||||
fn post_config(conn: DbConn, data: Form<NewInstanceForm>) -> Redirect {
|
||||
let form = data.get();
|
||||
let inst = Instance::insert(&*conn, NewInstance {
|
||||
public_domain: BASE_URL.as_str().to_string(),
|
||||
name: form.name.to_string(),
|
||||
local: true
|
||||
});
|
||||
if inst.has_admin(&*conn) {
|
||||
Redirect::to("/")
|
||||
} else {
|
||||
Redirect::to("/users/new")
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/inbox", data = "<data>")]
|
||||
fn shared_inbox(conn: DbConn, data: String) -> String {
|
||||
let act: serde_json::Value = serde_json::from_str(&data[..]).unwrap();
|
||||
|
||||
+5
-6
@@ -164,7 +164,6 @@ struct NewUserForm {
|
||||
|
||||
#[post("/users/new", data = "<data>")]
|
||||
fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> {
|
||||
let inst = Instance::get_local(&*conn).unwrap();
|
||||
let form = data.get();
|
||||
|
||||
if form.username.clone().len() < 1 {
|
||||
@@ -174,15 +173,15 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> {
|
||||
} else if form.password.clone().len() < 8 {
|
||||
Err(String::from("Password should be at least 8 characters long"))
|
||||
} else if form.password == form.password_confirmation {
|
||||
User::insert(&*conn, NewUser::new_local(
|
||||
NewUser::new_local(
|
||||
&*conn,
|
||||
form.username.to_string(),
|
||||
form.username.to_string(),
|
||||
!inst.has_admin(&*conn),
|
||||
false,
|
||||
String::from(""),
|
||||
form.email.to_string(),
|
||||
User::hash_pass(form.password.to_string()),
|
||||
inst.id
|
||||
)).update_boxes(&*conn);
|
||||
User::hash_pass(form.password.to_string())
|
||||
).update_boxes(&*conn);
|
||||
Ok(Redirect::to(uri!(super::session::new)))
|
||||
} else {
|
||||
Err(String::from("Passwords don't match"))
|
||||
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
use colored::Colorize;
|
||||
use diesel::{pg::PgConnection, r2d2::{ConnectionManager, Pool}};
|
||||
use dotenv::dotenv;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::{exit, Command};
|
||||
use rpassword;
|
||||
|
||||
use DB_URL;
|
||||
use db_conn::DbConn;
|
||||
use models::instance::*;
|
||||
use models::users::*;
|
||||
|
||||
type PgPool = Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
/// Initializes a database pool.
|
||||
fn init_pool() -> Option<PgPool> {
|
||||
dotenv().ok();
|
||||
|
||||
let manager = ConnectionManager::<PgConnection>::new(DB_URL.as_str());
|
||||
Pool::new(manager).ok()
|
||||
}
|
||||
|
||||
pub fn check() -> PgPool {
|
||||
if let Some(pool) = init_pool() {
|
||||
match pool.get() {
|
||||
Ok(conn) => {
|
||||
let db_conn = DbConn(conn);
|
||||
if Instance::get_local(&*db_conn).is_none() {
|
||||
run_setup(Some(db_conn));
|
||||
}
|
||||
}
|
||||
Err(_) => panic!("Couldn't connect to database")
|
||||
}
|
||||
migrate();
|
||||
pool
|
||||
} else {
|
||||
run_setup(None);
|
||||
init_pool().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn run_setup(conn: Option<DbConn>) {
|
||||
println!("\n\n");
|
||||
println!("{}\n{}\n{}\n\n{}",
|
||||
"Welcome in the Plume setup tool.".magenta(),
|
||||
"It will help you setup your new instance, by asking you a few questions.".magenta(),
|
||||
"Then you'll be able to enjoy Plume!".magenta(),
|
||||
"First let's check that you have all the required dependencies. Press Enter to start."
|
||||
);
|
||||
read_line();
|
||||
check_native_deps();
|
||||
let conn = setup_db(conn);
|
||||
setup_type(conn);
|
||||
dotenv().ok();
|
||||
|
||||
println!("{}\n{}\n{}",
|
||||
"Your Plume instance is now ready to be used.".magenta(),
|
||||
"We hope you will enjoy it.".magenta(),
|
||||
"If you ever encounter a problem, feel free to report it at https://github.com/Plume-org/Plume/issues/".magenta(),
|
||||
);
|
||||
|
||||
println!("\nPress Enter to start it.\n");
|
||||
}
|
||||
|
||||
fn setup_db(conn: Option<DbConn>) -> DbConn {
|
||||
write_to_dotenv("DB_URL", DB_URL.as_str().to_string());
|
||||
|
||||
match conn {
|
||||
Some(conn) => conn,
|
||||
None => {
|
||||
println!("\n{}\n", "We are going to setup the database.".magenta());
|
||||
println!("{}\n", "About to create a new PostgreSQL user named 'plume'".blue());
|
||||
Command::new("createuser")
|
||||
.arg("-d")
|
||||
.arg("-P")
|
||||
.arg("plume")
|
||||
.status()
|
||||
.map(|s| {
|
||||
if s.success() {
|
||||
println!("{}\n", " ✔️ Done".green());
|
||||
}
|
||||
})
|
||||
.expect("Couldn't create new user");
|
||||
|
||||
println!("{}\n", "About to create a new PostgreSQL database named 'plume'".blue());
|
||||
Command::new("createdb")
|
||||
.arg("-O")
|
||||
.arg("plume")
|
||||
.arg("plume")
|
||||
.status()
|
||||
.map(|s| {
|
||||
if s.success() {
|
||||
println!("{}\n", " ✔️ Done".green());
|
||||
}
|
||||
})
|
||||
.expect("Couldn't create new table");
|
||||
|
||||
migrate();
|
||||
|
||||
init_pool()
|
||||
.expect("Couldn't init DB pool")
|
||||
.get()
|
||||
.map(|c| DbConn(c))
|
||||
.expect("Couldn't connect to the database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate() {
|
||||
println!("{}\n", "Running migrations…".blue());
|
||||
Command::new("diesel")
|
||||
.arg("migration")
|
||||
.arg("run")
|
||||
.arg("--database-url")
|
||||
.arg(DB_URL.as_str())
|
||||
.status()
|
||||
.map(|s| {
|
||||
if s.success() {
|
||||
println!("{}\n", " ✔️ Done".green());
|
||||
}
|
||||
})
|
||||
.expect("Couldn't run migrations");
|
||||
}
|
||||
|
||||
fn setup_type(conn: DbConn) {
|
||||
println!("\nDo you prefer a simple setup, or to customize everything?\n");
|
||||
println!(" 1 - Simple setup");
|
||||
println!(" 2 - Complete setup");
|
||||
match read_line().as_ref() {
|
||||
"Simple" | "simple" | "s" | "S" |
|
||||
"1" => quick_setup(conn),
|
||||
"Complete" | "complete" | "c" | "C" |
|
||||
"2" => complete_setup(conn),
|
||||
x => {
|
||||
println!("Invalid choice. Choose between '1' or '2'. {}", x);
|
||||
setup_type(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn quick_setup(conn: DbConn) {
|
||||
println!("What is your instance domain?");
|
||||
let domain = read_line();
|
||||
write_to_dotenv("BASE_URL", domain.clone());
|
||||
|
||||
println!("\nWhat is your instance name?");
|
||||
let name = read_line();
|
||||
|
||||
let instance = Instance::insert(&*conn, NewInstance {
|
||||
public_domain: domain,
|
||||
name: name,
|
||||
local: true
|
||||
});
|
||||
|
||||
println!("{}\n", " ✔️ Your instance was succesfully created!".green());
|
||||
|
||||
// Generate Rocket secret key.
|
||||
let key = Command::new("openssl")
|
||||
.arg("rand")
|
||||
.arg("-base64")
|
||||
.arg("32")
|
||||
.output()
|
||||
.map(|o| String::from_utf8(o.stdout).expect("Invalid output from openssl"))
|
||||
.expect("Couldn't generate secret key.");
|
||||
write_to_dotenv("ROCKET_SECRET_KEY", key);
|
||||
|
||||
create_admin(instance, conn);
|
||||
}
|
||||
|
||||
fn complete_setup(conn: DbConn) {
|
||||
quick_setup(conn);
|
||||
|
||||
println!("\nOn which port should Plume listen? (default: 7878)");
|
||||
let port = read_line_or("7878");
|
||||
write_to_dotenv("ROCKET_PORT", port);
|
||||
|
||||
println!("\nOn which address should Plume listen? (default: 0.0.0.0)");
|
||||
let address = read_line_or("0.0.0.0");
|
||||
write_to_dotenv("ROCKET_ADDRESS", address);
|
||||
}
|
||||
|
||||
fn create_admin(instance: Instance, conn: DbConn) {
|
||||
println!("{}\n\n", "You are now about to create your admin account".magenta());
|
||||
|
||||
println!("What is your username? (default: admin)");
|
||||
let name = read_line_or("admin");
|
||||
|
||||
println!("What is your email?");
|
||||
let email = read_line();
|
||||
|
||||
println!("What is your password?");
|
||||
let password = rpassword::read_password().expect("Couldn't read your password.");
|
||||
|
||||
NewUser::new_local(
|
||||
&*conn,
|
||||
name.clone(),
|
||||
name,
|
||||
true,
|
||||
format!("Admin of {}", instance.name),
|
||||
email,
|
||||
User::hash_pass(password),
|
||||
).update_boxes(&*conn);
|
||||
|
||||
println!("{}\n", " ✔️ Your account was succesfully created!".green());
|
||||
}
|
||||
|
||||
fn check_native_deps() {
|
||||
let mut not_found = Vec::new();
|
||||
if !try_run("psql") {
|
||||
not_found.push(("PostgreSQL", "sudo apt install postgres"));
|
||||
}
|
||||
if !try_run("gettext") {
|
||||
not_found.push(("GetText", "sudo apt install gettext"))
|
||||
}
|
||||
if !try_run("diesel") {
|
||||
not_found.push(("Diesel CLI", "cargo install diesel_cli"))
|
||||
}
|
||||
|
||||
if not_found.len() > 0 {
|
||||
println!("{}\n", "Some native dependencies are missing:".red());
|
||||
for (dep, install) in not_found.into_iter() {
|
||||
println!("{}", format!(" - {} (can be installed with `{}`, on Debian based distributions)", dep, install).red())
|
||||
}
|
||||
println!("\nRetry once you have installed them.");
|
||||
exit(1);
|
||||
} else {
|
||||
println!("{}", " ✔️ All native dependencies are present.".green())
|
||||
}
|
||||
}
|
||||
|
||||
fn try_run(command: &'static str) -> bool {
|
||||
Command::new(command)
|
||||
.output()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn read_line() -> String {
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).expect("Unable to read line");
|
||||
input.retain(|c| c != '\n');
|
||||
input
|
||||
}
|
||||
|
||||
fn read_line_or(or: &str) -> String {
|
||||
let input = read_line();
|
||||
if input.len() == 0 {
|
||||
or.to_string()
|
||||
} else {
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_dotenv(var: &'static str, val: String) {
|
||||
if !Path::new(".env").exists() {
|
||||
File::create(".env").expect("Error while creating .env file");
|
||||
}
|
||||
|
||||
fs::write(".env", format!("{}\n{}={}", fs::read_to_string(".env").expect("Unable to read .env"), var, val)).expect("Unable to write .env");
|
||||
}
|
||||
Reference in New Issue
Block a user