Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
21e1bb2173 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,6 +4,3 @@ cli/capsule/
|
|||||||
db.db3
|
db.db3
|
||||||
cert.pem
|
cert.pem
|
||||||
key.rsa
|
key.rsa
|
||||||
content/
|
|
||||||
.DS_Store
|
|
||||||
build/
|
|
@ -1,11 +1,19 @@
|
|||||||
pipeline:
|
pipeline:
|
||||||
|
|
||||||
|
buildCli:
|
||||||
|
group: build
|
||||||
|
image: rust
|
||||||
|
commands:
|
||||||
|
- cd cli
|
||||||
|
- rustup target add x86_64-apple-darwin
|
||||||
|
- cargo build --release --target x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
buildApi:
|
buildApi:
|
||||||
group: build
|
group: build
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
secrets: [docker_username, docker_password]
|
||||||
when:
|
when:
|
||||||
path: "api/**/*"
|
path: "api/*"
|
||||||
settings:
|
settings:
|
||||||
repo: wilw/capsuletown-api
|
repo: wilw/capsuletown-api
|
||||||
dockerfile: api/Dockerfile
|
dockerfile: api/Dockerfile
|
||||||
@ -16,46 +24,22 @@ pipeline:
|
|||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
secrets: [docker_username, docker_password]
|
||||||
when:
|
when:
|
||||||
path: "server/**/*"
|
path: "server/*"
|
||||||
settings:
|
settings:
|
||||||
repo: wilw/capsuletown-server
|
repo: wilw/capsuletown-server
|
||||||
dockerfile: server/Dockerfile
|
dockerfile: server/Dockerfile
|
||||||
context: server
|
context: server
|
||||||
|
|
||||||
buildCli:
|
|
||||||
group: build
|
|
||||||
image: rust
|
|
||||||
when:
|
|
||||||
path: "cli/**/*"
|
|
||||||
commands:
|
|
||||||
- cd cli
|
|
||||||
- cargo build --release
|
|
||||||
- target/release/captown help
|
|
||||||
|
|
||||||
deployRootCapsule:
|
deployRootCapsule:
|
||||||
group: deploy
|
|
||||||
image: python:3.10
|
image: python:3.10
|
||||||
|
group: build
|
||||||
when:
|
when:
|
||||||
path: "home/**/*"
|
path: "home/*"
|
||||||
secrets: [ CAPSULE_TOWN_KEY ]
|
secrets: [ CAPSULE_TOWN_KEY ]
|
||||||
commands:
|
commands:
|
||||||
- cd home
|
- cd home
|
||||||
- CAPSULE=$(tar -czf /tmp/c.tar.gz -C capsule . && cat /tmp/c.tar.gz | base64)
|
- CAPSULE=$(tar -czf /tmp/c.tar.gz -C capsule . && cat /tmp/c.tar.gz | base64)
|
||||||
- 'echo "{\"capsuleArchive\": \"$CAPSULE\"}" > /tmp/capsule_file'
|
- 'echo "{\"capsuleArchive\": \"$CAPSULE\"}" > /tmp/capsule_file'
|
||||||
- 'curl -X PUT -H "Content-Type: application/json" -H "api-key: $CAPSULE_TOWN_KEY" -d @/tmp/capsule_file https://api.capsule.town/capsule'
|
- 'curl -X PUT -H "Content-Type: application/json" -H "api-key: $CAPSULE_TOWN_KEY" -d @/tmp/capsule_file https://api.capsule.town/capsule'
|
||||||
|
|
||||||
deployCli:
|
|
||||||
group: deploy
|
|
||||||
image: alpine
|
|
||||||
when:
|
|
||||||
path: "cli/**/*"
|
|
||||||
secrets: [ LINODE_ACCESS_KEY, LINODE_SECRET_ACCESS_KEY, BUNNY_KEY ]
|
|
||||||
commands:
|
|
||||||
- cd cli
|
|
||||||
- apk update
|
|
||||||
- apk add s3cmd curl
|
|
||||||
- s3cmd --configure --access_key=$LINODE_ACCESS_KEY --secret_key=$LINODE_SECRET_ACCESS_KEY --host=https://eu-central-1.linodeobjects.com --host-bucket="%(bucket)s.eu-central-1.linodeobjects.com" --dump-config > /root/.s3cfg
|
|
||||||
- s3cmd -c /root/.s3cfg put target/release/captown s3://download.capsule.town/captown-linux-0.2 --acl-public
|
|
||||||
- 'curl -X POST -H "AccessKey: $BUNNY_KEY" https://api.bunny.net/pullzone/783552/purgeCache'
|
|
||||||
|
|
||||||
branches: main
|
#branches: main
|
640
api/Cargo.lock
generated
640
api/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,14 +4,15 @@ version = "0.1.0"
|
|||||||
authors = ["Will Webberley <me@wilw.dev>"]
|
authors = ["Will Webberley <me@wilw.dev>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = "0.4.11"
|
rocket = "0.4.8"
|
||||||
jsonwebtoken = "8.3.0"
|
rocket_contrib = "0.4.6"
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
jsonwebtoken = "7.2"
|
||||||
flate2 = "1.0.26"
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tar = "0.4.38"
|
flate2 = "1.0.19"
|
||||||
base64 = "0.21.0"
|
tar = "0.4.30"
|
||||||
rusqlite = "0.29.0"
|
base64 = "0.13.0"
|
||||||
uuid = { version = "1.3.1", features = ["v4"] }
|
rusqlite = "0.24.2"
|
||||||
ubyte = "0.10.3"
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
rocket_contrib = "0.4.11"
|
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
#[macro_use] extern crate rocket_contrib;
|
#[macro_use] extern crate rocket_contrib;
|
||||||
|
|
||||||
use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write, collections::HashSet};
|
use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write};
|
||||||
use rocket::{self, routes, get, post, put, catch, config::{Config, Limits}, response::Responder, request::{self, Request, FromRequest}, Outcome, http::Status};
|
use rocket::{self, routes, get, post, put, config::{Config, Limits}, response::Responder, request::{self, Request, FromRequest}, Outcome, http::Status};
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket_contrib::json::{Json, JsonValue};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
|
use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey};
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
const DB_FILE: &str = "/usr/local/var/capsules_db/db.db3";
|
const DB_FILE: &str = "/usr/local/var/capsules_db/db.db3";
|
||||||
@ -73,9 +73,10 @@ fn generate_access_token(capsule_id: &str) -> Result<String> {
|
|||||||
// Verify the access JWT token, and checks still valid in DB. Returns the authenticated capsule.
|
// Verify the access JWT token, and checks still valid in DB. Returns the authenticated capsule.
|
||||||
fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> {
|
fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> {
|
||||||
// Validate the token integrity itself
|
// Validate the token integrity itself
|
||||||
let mut my_validation = Validation::new(Algorithm::HS256);
|
let my_validation = Validation {
|
||||||
my_validation.validate_exp = false;
|
validate_exp: false,
|
||||||
my_validation.required_spec_claims = HashSet::new();
|
..Default::default()
|
||||||
|
};
|
||||||
let claims = decode::<Claims>(&token, &DecodingKey::from_secret(get_jwt_secret().as_ref()), &my_validation)?.claims;
|
let claims = decode::<Claims>(&token, &DecodingKey::from_secret(get_jwt_secret().as_ref()), &my_validation)?.claims;
|
||||||
|
|
||||||
// Verify the token in the DB and retrieve the capsule info
|
// Verify the token in the DB and retrieve the capsule info
|
||||||
@ -104,11 +105,6 @@ enum Resp {
|
|||||||
NotFound(JsonValue),
|
NotFound(JsonValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(400)]
|
|
||||||
fn bad_request(_: &Request) -> Resp {
|
|
||||||
return Resp::BadRequest(json!({"message": "There was a problem with your request"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> Resp {
|
fn index() -> Resp {
|
||||||
return Resp::NotFound(json!({"message": "There is no resource available at this path."}));
|
return Resp::NotFound(json!({"message": "There is no resource available at this path."}));
|
||||||
@ -236,7 +232,7 @@ struct CapsuleDeployRequest {
|
|||||||
#[put("/capsule", data = "<capsule>")]
|
#[put("/capsule", data = "<capsule>")]
|
||||||
fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: AuthenticatedCapsule) -> Resp {
|
fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: AuthenticatedCapsule) -> Resp {
|
||||||
// Decode base64-encoded JSON capsule_archive. This gives a Vec<u8>
|
// Decode base64-encoded JSON capsule_archive. This gives a Vec<u8>
|
||||||
let archive_vec: Vec<u8> = match general_purpose::STANDARD.decode(&capsule.capsule_archive) {
|
let archive_vec: Vec<u8> = match base64::decode(&capsule.capsule_archive) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return Resp::BadRequest(json!({"message": "Your capsule archive is not valid"})),
|
Err(_) => return Resp::BadRequest(json!({"message": "Your capsule archive is not valid"})),
|
||||||
};
|
};
|
||||||
@ -271,30 +267,6 @@ fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: Au
|
|||||||
return Resp::Ok(json!({"message": "Your capsule was deployed", "url": url}));
|
return Resp::Ok(json!({"message": "Your capsule was deployed", "url": url}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct CapsuleViewLog {
|
|
||||||
path: String,
|
|
||||||
timestamp: i32,
|
|
||||||
}
|
|
||||||
#[get("/logs")]
|
|
||||||
fn get_logs(authenticated_capsule: AuthenticatedCapsule) -> Resp {
|
|
||||||
let conn = match get_db() {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(_) => return Resp::InternalServerError(json!({"message": "Database problem"})),
|
|
||||||
};
|
|
||||||
let mut stmt = conn
|
|
||||||
.prepare("SELECT path, timestamp FROM view WHERE capsule_id = ?1 ORDER BY timestamp DESC LIMIT 100")
|
|
||||||
.unwrap();
|
|
||||||
let view_results = stmt.query_map(params![authenticated_capsule.id], |row| {
|
|
||||||
Ok(CapsuleViewLog {
|
|
||||||
path: row.get(0)?,
|
|
||||||
timestamp: row.get(1)?,
|
|
||||||
})
|
|
||||||
}).unwrap();
|
|
||||||
let views: Vec<CapsuleViewLog> = view_results.map(|view| view.unwrap()).collect();
|
|
||||||
return Resp::Ok(json!({"logs": &views}));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Create needed directories
|
// Create needed directories
|
||||||
fs::create_dir_all(CAPSULE_DIR).unwrap_or_default();
|
fs::create_dir_all(CAPSULE_DIR).unwrap_or_default();
|
||||||
@ -304,5 +276,5 @@ fn main() {
|
|||||||
my_config.set_port(9000);
|
my_config.set_port(9000);
|
||||||
my_config.set_limits(Limits::new().limit("json", 50 * 1024 * 1024)); // 50MB
|
my_config.set_limits(Limits::new().limit("json", 50 * 1024 * 1024)); // 50MB
|
||||||
let app = rocket::custom(my_config);
|
let app = rocket::custom(my_config);
|
||||||
app.mount("/", routes![index, create_capsule, deploy_capsule, get_logs]).launch();
|
app.mount("/", routes![index, create_capsule, deploy_capsule]).launch();
|
||||||
}
|
}
|
||||||
|
1295
cli/Cargo.lock
generated
1295
cli/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,3 @@ reqwest = { version = "0.11", features = ["json", "blocking"] }
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
chrono = "0.4.24"
|
|
||||||
prettytable = "0.10.0"
|
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
|
||||||
|
102
cli/src/main.rs
102
cli/src/main.rs
@ -5,8 +5,6 @@ use clap::{Arg, App, SubCommand};
|
|||||||
use flate2::{Compression, write::GzEncoder};
|
use flate2::{Compression, write::GzEncoder};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use chrono::prelude::*;
|
|
||||||
use prettytable::{Table, row};
|
|
||||||
use dirs;
|
use dirs;
|
||||||
use base64;
|
use base64;
|
||||||
use reqwest;
|
use reqwest;
|
||||||
@ -207,81 +205,6 @@ fn create_capsule() -> Result<bool>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct LogsResponse {
|
|
||||||
logs: Option<Vec<Log>>,
|
|
||||||
message: Option<String>,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Log {
|
|
||||||
path: String,
|
|
||||||
timestamp: i64,
|
|
||||||
}
|
|
||||||
impl Log {
|
|
||||||
fn time(&self) -> String {
|
|
||||||
let naive = NaiveDateTime::from_timestamp_opt(self.timestamp, 0).unwrap();
|
|
||||||
let datetime: DateTime<Utc> = DateTime::from_utc(naive, Utc);
|
|
||||||
let newdate = format!("{}", datetime.format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
return newdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// PUTs the base64-encoded capsule tarball to the server.
|
|
||||||
fn capsule_logs(flag_capsule_name: Option<&str>, flag_number_of_logs: Option<&str>) -> Result<bool>{
|
|
||||||
let capsule_name: String = match flag_capsule_name {
|
|
||||||
Some(x) => String::from(x),
|
|
||||||
None => prompt_for("Which capsule do you want to view logs for?"),
|
|
||||||
};
|
|
||||||
let capsule_config = match read_config_capsule(&capsule_name) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(x) => {
|
|
||||||
println!("{:?}", x);
|
|
||||||
return Err(Box::from("Unable to get logs"));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let number_of_logs: usize = match flag_number_of_logs {
|
|
||||||
Some(x) => {
|
|
||||||
let required = match x.parse::<usize>() {
|
|
||||||
Ok(y) => y,
|
|
||||||
Err(_) => return Err(Box::from("Log count must be a number")),
|
|
||||||
};
|
|
||||||
if required > 100 {
|
|
||||||
return Err(Box::from("Maximum log count is 100"));
|
|
||||||
}
|
|
||||||
required
|
|
||||||
},
|
|
||||||
None => 20,
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
|
||||||
let res = client.get(&format!("{}/logs", get_api_url()))
|
|
||||||
.header("api-key", &capsule_config.access_token).send()?;
|
|
||||||
let status = res.status();
|
|
||||||
let response_data = res.json::<LogsResponse>()?;
|
|
||||||
if status.is_success() {
|
|
||||||
match response_data.logs {
|
|
||||||
None => println!("No logs found"),
|
|
||||||
Some(mut logs) => {
|
|
||||||
logs.truncate(number_of_logs);
|
|
||||||
logs.reverse();
|
|
||||||
let mut table = Table::new();
|
|
||||||
table.add_row(row!["TIME", "CAPSULE", "PATH"]);
|
|
||||||
for log in logs {
|
|
||||||
table.add_row(row![log.time(), capsule_name, log.path]);
|
|
||||||
}
|
|
||||||
table.printstd();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return Ok(true);
|
|
||||||
} else {
|
|
||||||
let error_message = match response_data.message {
|
|
||||||
None => String::from("Unknown error"),
|
|
||||||
Some(message) => message,
|
|
||||||
};
|
|
||||||
println!("Unable to get the logs for your capsule: {}", &error_message);
|
|
||||||
return Err(Box::from("Unable to get logs"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Setup config
|
// Setup config
|
||||||
@ -291,7 +214,7 @@ fn main() {
|
|||||||
|
|
||||||
// Read command-line commands/args
|
// Read command-line commands/args
|
||||||
let app_args = App::new("Capsule.Town")
|
let app_args = App::new("Capsule.Town")
|
||||||
.version("0.2")
|
.version("0.1")
|
||||||
.author("Will W. (wilw.dev)")
|
.author("Will W. (wilw.dev)")
|
||||||
.about("Publishes your capsule to capsule.town")
|
.about("Publishes your capsule to capsule.town")
|
||||||
.subcommand(SubCommand::with_name("create")
|
.subcommand(SubCommand::with_name("create")
|
||||||
@ -302,19 +225,7 @@ fn main() {
|
|||||||
.short("c")
|
.short("c")
|
||||||
.long("capsule")
|
.long("capsule")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Name of the capsule to be published")))
|
.help("Name of the capsule to be published")));
|
||||||
.subcommand(SubCommand::with_name("logs")
|
|
||||||
.about("View access logs for your capsule")
|
|
||||||
.arg(Arg::with_name("capsule")
|
|
||||||
.short("c")
|
|
||||||
.long("capsule")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Name of the capsule to view logs for"))
|
|
||||||
.arg(Arg::with_name("number")
|
|
||||||
.short("n")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Number of logs to display (default: 20, maximum: 100)"))
|
|
||||||
);
|
|
||||||
let matches = app_args.get_matches();
|
let matches = app_args.get_matches();
|
||||||
|
|
||||||
// Handle commands
|
// Handle commands
|
||||||
@ -324,17 +235,12 @@ fn main() {
|
|||||||
let capsule_name = sub_match.value_of("capsule");
|
let capsule_name = sub_match.value_of("capsule");
|
||||||
publish_capsule(capsule_name)
|
publish_capsule(capsule_name)
|
||||||
},
|
},
|
||||||
("logs", Some(sub_match)) => {
|
|
||||||
let capsule_name = sub_match.value_of("capsule");
|
|
||||||
let number_of_logs = sub_match.value_of("number");
|
|
||||||
capsule_logs(capsule_name, number_of_logs)
|
|
||||||
},
|
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unknown command. Available commands: create, publish, logs");
|
println!("Unknown command. Available commands: create, publish");
|
||||||
Err(Box::from("Unable to continue"))
|
Err(Box::from("Unable to continue"))
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
println!("Exiting with error: {}", err);
|
println!("Program is exiting with error. {}", err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
✨ No sign-up is required and your capsule can be live within seconds.
|
✨ No sign-up is required and your capsule can be live within seconds.
|
||||||
✨ You get a free Geminispace hosted at <yoursubdomain>.capsule.town.
|
✨ You get a free Geminispace hosted at <yoursubdomain>.capsule.town.
|
||||||
✨ You can publish entirely anonymously.
|
✨ You can publish entirely anonymously.
|
||||||
|
✨ There is no logging, tracking, or analytics.
|
||||||
|
|
||||||
🚧 This project is currently in open beta. Feedback, bug-reports, and contributions are welcome. Please see below for ways to get in touch.
|
🚧 This project is currently in open beta. Feedback, bug-reports, and contributions are welcome. Please see below for ways to get in touch.
|
||||||
|
|
||||||
@ -38,9 +39,9 @@ Think of this service as the Gemini version of static site hosting services like
|
|||||||
|
|
||||||
## Why is it free?
|
## Why is it free?
|
||||||
|
|
||||||
capsule.town does not generate any income, and I pay for the servers and upkeep myself.
|
capsule.town does not generate any income, and I pay for the servers/upkeep myself.
|
||||||
|
|
||||||
This is one way I can give back to the indie and open-source community. Your data is not sold. You can view and evaluate the source yourself (there is a link above).
|
This is one way I can give back to the indie and open-source community. There is no logging or analytics and your data is not collected or sold. You can view and evaluate the source yourself (there is a link above).
|
||||||
|
|
||||||
By taking part you are certainly not the product; you are a member of the community.
|
By taking part you are certainly not the product; you are a member of the community.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
=> gemini://capsule.town Return home
|
=> gemini://capsule.town Return home
|
||||||
|
|
||||||
This page describes the rules applying to all capsules hosted on capsule.town.
|
This page describes the Rules applying to all capsules hosted on capsule.town.
|
||||||
|
|
||||||
By creating and publishing your capsule, you declare that you agree to these rules.
|
By creating and publishing your capsule, you declare that you agree to these rules.
|
||||||
|
|
||||||
|
@ -25,21 +25,21 @@ You can use CURL to get the client, as described below.
|
|||||||
For Linux:
|
For Linux:
|
||||||
|
|
||||||
```
|
```
|
||||||
➡️ curl https://download.capsule.town/captown-linux-0.2 -o captown
|
➡️ curl -O https://download.capsule.town/captown-linux-0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
For Mac:
|
For Mac:
|
||||||
|
|
||||||
```
|
```
|
||||||
➡️ curl https://download.capsule.town/captown-mac-0.2 -o captown
|
➡️ curl -O https://download.capsule.town/captown-mac-0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
ℹ️ We do not yet have a Windows version, but contributions are welcome.
|
ℹ️ The Windows client is coming soon (contributions welcome).
|
||||||
|
|
||||||
Once downloaded:
|
Once downloaded:
|
||||||
|
|
||||||
1. Mark the binary as executable: `chmod +x captown`
|
1. Mark the binary as executable: `chmod +x captown-mac-0.1`
|
||||||
2. Move the binary to a location of your choosing: e.g. `sudo mv captown /usr/local/bin`.
|
2. Move the binary to a location of your choosing: e.g. `mv captown-mac-0.1 /usr/local/bin/captown`.
|
||||||
|
|
||||||
The rest of this guide assumes the binary is somewhere on your `PATH` and can be invoked using `captown` (as above).
|
The rest of this guide assumes the binary is somewhere on your `PATH` and can be invoked using `captown` (as above).
|
||||||
|
|
||||||
@ -103,16 +103,6 @@ Whichever route you choose, if all goes well you'll then be able to visit `gemin
|
|||||||
|
|
||||||
🗒 Note: the `publish` command must be run from the root of your project (i.e. the directory containing the `capsule/` sub-directory).
|
🗒 Note: the `publish` command must be run from the root of your project (i.e. the directory containing the `capsule/` sub-directory).
|
||||||
|
|
||||||
### 5. View your capsule's logs
|
|
||||||
|
|
||||||
You can see an access log of your capsule by running the `logs` command:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ captown logs -c mycapsule
|
|
||||||
```
|
|
||||||
|
|
||||||
View other options for this command by running `captown help logs`.
|
|
||||||
|
|
||||||
## Making updates
|
## Making updates
|
||||||
|
|
||||||
When you've made updates that you want to publish, simply run `captown publish` again to make them live.
|
When you've made updates that you want to publish, simply run `captown publish` again to make them live.
|
||||||
|
117
server/Cargo.lock
generated
117
server/Cargo.lock
generated
@ -1,7 +1,5 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agate"
|
name = "agate"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@ -12,24 +10,12 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rusqlite",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -53,12 +39,6 @@ version = "0.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@ -101,18 +81,6 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fallible-iterator"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fallible-streaming-iterator"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -132,35 +100,6 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.2.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashlink"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
@ -204,19 +143,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.142"
|
version = "0.2.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libsqlite3-sys"
|
|
||||||
version = "0.26.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
|
|
||||||
dependencies = [
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
@ -299,9 +228,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
@ -315,12 +244,6 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
|
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.26"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@ -354,20 +277,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rusqlite"
|
|
||||||
version = "0.29.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"fallible-iterator",
|
|
||||||
"fallible-streaming-iterator",
|
|
||||||
"hashlink",
|
|
||||||
"libsqlite3-sys",
|
|
||||||
"smallvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@ -391,12 +300,6 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smallvec"
|
|
||||||
version = "1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.3.19"
|
version = "0.3.19"
|
||||||
@ -532,24 +435,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.69"
|
version = "0.2.69"
|
||||||
|
@ -21,7 +21,6 @@ once_cell = "1.4"
|
|||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
rustls = "0.19.0"
|
rustls = "0.19.0"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
rusqlite = "0.29.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -14,7 +14,6 @@ use {
|
|||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
},
|
},
|
||||||
tokio::{
|
tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
@ -23,16 +22,8 @@ use {
|
|||||||
},
|
},
|
||||||
tokio_rustls::{TlsAcceptor, server::TlsStream},
|
tokio_rustls::{TlsAcceptor, server::TlsStream},
|
||||||
url::{Host, Url},
|
url::{Host, Url},
|
||||||
rusqlite::{Connection},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DB_FILE: &str = "/usr/local/var/capsules_db/db.db3";
|
|
||||||
|
|
||||||
// Utility function to retrieve a connection to the database
|
|
||||||
fn get_db() -> Result<Connection, rusqlite::Error> {
|
|
||||||
return Ok(Connection::open(DB_FILE)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result {
|
fn main() -> Result {
|
||||||
if !ARGS.silent {
|
if !ARGS.silent {
|
||||||
env_logger::Builder::new().parse_filters("info").init();
|
env_logger::Builder::new().parse_filters("info").init();
|
||||||
@ -252,7 +243,7 @@ async fn send_response(url: Url, stream: &mut TlsStream<TcpStream>) -> Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the file opens successfully before sending the success header.
|
// Make sure the file opens successfully before sending the success header.
|
||||||
let mut file = match tokio::fs::File::open(&path).await {
|
let mut file = match tokio::fs::File::open(&path).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
@ -272,26 +263,6 @@ async fn send_response(url: Url, stream: &mut TlsStream<TcpStream>) -> Result {
|
|||||||
|
|
||||||
// Send body.
|
// Send body.
|
||||||
tokio::io::copy(&mut file, stream).await?;
|
tokio::io::copy(&mut file, stream).await?;
|
||||||
|
|
||||||
// Capture the view
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Capsule {
|
|
||||||
id: String,
|
|
||||||
}
|
|
||||||
let conn = get_db().unwrap();
|
|
||||||
let mut stmt = conn.prepare(&format!("SELECT id, name FROM capsule WHERE name = '{}'", &subdomain))?;
|
|
||||||
let mut capsule_cursor = stmt.query_map([], |row| {
|
|
||||||
Ok(Capsule {
|
|
||||||
id: row.get(0)?,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
let capsule = capsule_cursor.next().unwrap()?;
|
|
||||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
|
||||||
_ = conn.execute("CREATE TABLE IF NOT EXISTS view (capsule_id TEXT, path TEXT, timestamp INTEGER)", ());
|
|
||||||
conn.execute("INSERT INTO view (capsule_id, path, timestamp) VALUES (?1, ?2, ?3)",
|
|
||||||
(&capsule.id, &url.path(), timestamp),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user