Merge branch 'master' of github.com:Plume-org/Plume

This commit is contained in:
Bat 2018-06-20 10:02:05 +01:00
commit b9951f0d70
9 changed files with 306 additions and 88 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ rls
rls
translations
po/*.po~
.env

22
Cargo.lock generated
View File

@ -245,6 +245,14 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "colored"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "comrak"
version = "0.2.12"
@ -999,6 +1007,7 @@ dependencies = [
"base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bcrypt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1015,6 +1024,7 @@ dependencies = [
"rocket_codegen 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_contrib 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)",
"rocket_i18n 0.1.1 (git+https://github.com/BaptisteGelez/rocket_i18n?rev=5b4225d5bed5769482dc926a7e6d6b79f1217be6)",
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1282,6 +1292,16 @@ dependencies = [
"tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rpassword"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.7"
@ -1998,6 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
"checksum comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "053b26c8ce23b4c505a9479beace98f95899e0bf5c5255cf0219e9b0f48cf6ea"
"checksum cookie 0.11.0-dev (git+https://github.com/alexcrichton/cookie-rs?rev=0365a18)" = "<none>"
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
@ -2110,6 +2131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rocket_contrib 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_http 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)" = "<none>"
"checksum rocket_i18n 0.1.1 (git+https://github.com/BaptisteGelez/rocket_i18n?rev=5b4225d5bed5769482dc926a7e6d6b79f1217be6)" = "<none>"
"checksum rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d127299b02abda51634f14025aec43ae87a7aa7a95202b6a868ec852607d1451"
"checksum rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
"checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade"

View File

@ -8,6 +8,7 @@ ammonia = "1.1.0"
array_tool = "1.0"
base64 = "0.9"
bcrypt = "0.2"
colored = "1.6"
comrak = "0.2"
dotenv = "*"
failure = "0.1"
@ -19,6 +20,7 @@ hyper = "*"
lazy_static = "*"
openssl = "0.10.6"
reqwest = "0.8"
rpassword = "2.0"
serde = "*"
serde_derive = "1.0"
serde_json = "1.0"

View File

@ -42,46 +42,16 @@ Now, you can use the following command to start Postgres on a one-time basis.
pg_ctl -D /usr/local/var/postgres start
```
After starting Postgres, we need to enter [PSQL](http://postgresguide.com/utilities/psql.html), the interactive terminal for running postgres queries. We'll be running this as the user `postgres` which is an admin-type postgres user.
```
psql postgres
```
Now that you are in psql, enter the following queries to prepare the database for Plume.
```
CREATE DATABASE plume;
CREATE USER plume WITH PASSWORD 'plume';
GRANT ALL PRIVILEGES ON DATABASE plume to plume;
\q
```
The final command `\q` lets us exit psql and returns us to the Terminal. Now, we will open psql again, this time as the `plume` user we just created. Then we'll give all privileges on all tables and sequences to our `plume` user. This is for local development use only and it's not recommend to give complete access to this user in a production environment.
```
psql plume
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO plume;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO plume;
\q
```
When you will launch Plume for the first time, it will setup the database by itself.
#### Database Migration
Now that the Postgres database is set up and the `plume` user has the privileges it needs, we can set up the database using the diesel CLI. If this was your time installing Rust, you
will probably need to run that using `cargo`. `cargo` is installed with `rustc` so if you followed the earlier instructions it will already be available.
To run migrations and correctly setup the database, Plume use the `diesel` CLI tool under the hood. Therefore you should install it before running Plume. If this was your time installing Rust, you will probably need to run that using `cargo`. `cargo` is installed with `rustc` so if you followed the earlier instructions it will already be available.
```
cargo install diesel_cli
```
The first time you run this, you can run setup. After that, every time you pull the repository you will want to run the migration command in case there were any migrations. Those commands are
```
diesel setup --database-url='postgres://localhost/plume'
diesel migration run --database-url='postgres://localhost/plume'
```
#### Running Plume
To run Plume locally, make sure you are once again in the Plume directory, such as `~/dev/Plume`. Now you will be able to run the application using the command
@ -92,7 +62,7 @@ cargo run
#### Configuration
Now Plume should be running on your machine at [http://localhost:8000](http://localhost:8000). The first time you run the application, you'll want to configure your blog name on the [http://localhost:8000/configure](http://localhost:8000/configure) page. You'll be able to change this name later.
The first time you'll run Plume, it will help you setup your instance through an interactive tool. Once you'll have answered all its question, your instance will start.
#### Testing the federation

View File

@ -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);
}))

View File

@ -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
}
})
}
}

View File

@ -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();

View File

@ -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
src/setup.rs Normal file
View File

@ -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");
}