Compare commits
No commits in common. "main" and "f1135aa97fe41f72d79d3c0ef49689881c3b7071" have entirely different histories.
main
...
f1135aa97f
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,9 +1,6 @@
|
|||||||
target/
|
target/
|
||||||
cli/capsule/
|
capsule/
|
||||||
*.sw*
|
*.sw*
|
||||||
db.db3
|
db.db3
|
||||||
cert.pem
|
cert.pem
|
||||||
key.rsa
|
key.rsa
|
||||||
content/
|
|
||||||
.DS_Store
|
|
||||||
build/
|
|
@ -1,61 +0,0 @@
|
|||||||
pipeline:
|
|
||||||
|
|
||||||
buildApi:
|
|
||||||
group: build
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
when:
|
|
||||||
path: "api/**/*"
|
|
||||||
settings:
|
|
||||||
repo: wilw/capsuletown-api
|
|
||||||
dockerfile: api/Dockerfile
|
|
||||||
context: api
|
|
||||||
|
|
||||||
buildServer:
|
|
||||||
group: build
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
when:
|
|
||||||
path: "server/**/*"
|
|
||||||
settings:
|
|
||||||
repo: wilw/capsuletown-server
|
|
||||||
dockerfile: server/Dockerfile
|
|
||||||
context: server
|
|
||||||
|
|
||||||
buildCli:
|
|
||||||
group: build
|
|
||||||
image: rust
|
|
||||||
when:
|
|
||||||
path: "cli/**/*"
|
|
||||||
commands:
|
|
||||||
- cd cli
|
|
||||||
- cargo build --release
|
|
||||||
- target/release/captown help
|
|
||||||
|
|
||||||
deployRootCapsule:
|
|
||||||
group: deploy
|
|
||||||
image: python:3.10
|
|
||||||
when:
|
|
||||||
path: "home/**/*"
|
|
||||||
secrets: [ CAPSULE_TOWN_KEY ]
|
|
||||||
commands:
|
|
||||||
- cd home
|
|
||||||
- CAPSULE=$(tar -czf /tmp/c.tar.gz -C capsule . && cat /tmp/c.tar.gz | base64)
|
|
||||||
- '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'
|
|
||||||
|
|
||||||
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
|
|
13
README.md
13
README.md
@ -1,13 +0,0 @@
|
|||||||
# capsule.town
|
|
||||||
|
|
||||||
Welcome to the capsule.town repository.
|
|
||||||
|
|
||||||
This repository contains the files and source for the client, API, and server that power gemini://capsule.town.
|
|
||||||
|
|
||||||
# Learn more
|
|
||||||
|
|
||||||
To find out more, visit gemini://capsule.town with a Gemini client.
|
|
||||||
|
|
||||||
# Contribute
|
|
||||||
|
|
||||||
If you'd like to contribute to this project, please [get in touch](https://wilw.dev).
|
|
642
api/Cargo.lock
generated
642
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.6"
|
||||||
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"
|
|
||||||
|
26
api/LICENSE
26
api/LICENSE
@ -1,26 +0,0 @@
|
|||||||
BSD 2-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2021, Will Webberley
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
|||||||
// Copyright (c) 2021, Will Webberley. See LICENSE.
|
|
||||||
|
|
||||||
#![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 +71,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 +103,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 +230,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 +265,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 +274,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();
|
||||||
}
|
}
|
||||||
|
1355
cli/Cargo.lock
generated
1355
cli/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "captown"
|
name = "gem-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Will Webberley <me@wilw.dev>"]
|
authors = ["Will Webberley <me@wilw.dev>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -10,13 +10,10 @@ edition = "2018"
|
|||||||
flate2 = "1.0.19"
|
flate2 = "1.0.19"
|
||||||
tar = "0.4.30"
|
tar = "0.4.30"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
clap = "2.33.3"
|
openssl = "0.10.32"
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
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"] }
|
|
||||||
|
26
cli/LICENSE
26
cli/LICENSE
@ -1,26 +0,0 @@
|
|||||||
BSD 2-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2021, Will Webberley
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
# capsule.town CLI client
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To cross-compile Linux:
|
|
||||||
|
|
||||||
````
|
|
||||||
docker run --rm --user "$(id -u)":"$(id -g)" -v "$PWD":/usr/src/app -w /usr/src/app rust cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release
|
|
||||||
|
|
||||||
Upload to object storage:
|
|
||||||
|
|
||||||
```
|
|
||||||
3cmd put target/release/captown s3://capsuletown/cli/captown-linux-0.1 --acl-public
|
|
||||||
```
|
|
141
cli/src/main.rs
141
cli/src/main.rs
@ -1,12 +1,7 @@
|
|||||||
// Copyright (c) 2021, Will Webberley. See LICENSE
|
|
||||||
|
|
||||||
use std::{env, str, io::{stdin,stdout,Write}, collections::HashMap, fs::{self, File}, path::{Path, PathBuf}};
|
use std::{env, str, io::{stdin,stdout,Write}, collections::HashMap, fs::{self, File}, path::{Path, PathBuf}};
|
||||||
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;
|
||||||
@ -133,11 +128,8 @@ struct DeployResponse {
|
|||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
// PUTs the base64-encoded capsule tarball to the server.
|
// PUTs the base64-encoded capsule tarball to the server.
|
||||||
fn publish_capsule(flag_capsule_name: Option<&str>) -> Result<bool>{
|
fn publish_capsule() -> Result<bool>{
|
||||||
let capsule_name: String = match flag_capsule_name {
|
let capsule_name: String = prompt_for("Enter the name for this capsule");
|
||||||
Some(x) => String::from(x),
|
|
||||||
None => prompt_for("Enter the name for this capsule"),
|
|
||||||
};
|
|
||||||
let capsule_config = read_config_capsule(&capsule_name).expect("The configuration for your specified capsule could not be found.");
|
let capsule_config = read_config_capsule(&capsule_name).expect("The configuration for your specified capsule could not be found.");
|
||||||
|
|
||||||
let base64_encoded_capsule = match read_directory_as_string() {
|
let base64_encoded_capsule = match read_directory_as_string() {
|
||||||
@ -207,81 +199,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
|
||||||
@ -289,52 +206,24 @@ fn main() {
|
|||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read command-line commands/args
|
// Parse command
|
||||||
let app_args = App::new("Capsule.Town")
|
let args: Vec<String> = env::args().collect();
|
||||||
.version("0.2")
|
if args.len() < 2 {
|
||||||
.author("Will W. (wilw.dev)")
|
println!("Please specify a command");
|
||||||
.about("Publishes your capsule to capsule.town")
|
return;
|
||||||
.subcommand(SubCommand::with_name("create")
|
}
|
||||||
.about("Creates a new capsule"))
|
let command = &args[1];
|
||||||
.subcommand(SubCommand::with_name("publish")
|
|
||||||
.about("Publishes your capsule")
|
|
||||||
.arg(Arg::with_name("capsule")
|
|
||||||
.short("c")
|
|
||||||
.long("capsule")
|
|
||||||
.takes_value(true)
|
|
||||||
.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();
|
|
||||||
|
|
||||||
// Handle commands
|
// Handle command
|
||||||
if let Err(err) = match matches.subcommand() {
|
if let Err(err) = match command.as_str() {
|
||||||
("create", Some(_)) => create_capsule(),
|
"create" => create_capsule(),
|
||||||
("publish", Some(sub_match)) => {
|
"publish" => publish_capsule(),
|
||||||
let capsule_name = sub_match.value_of("capsule");
|
|
||||||
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", &command);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
# capsule.town home
|
|
||||||
|
|
||||||
This directory contains the Gemini files that form the gemini://capsule.town homepage.
|
|
@ -1,58 +0,0 @@
|
|||||||
```
|
|
||||||
_
|
|
||||||
| |
|
|
||||||
___ __ _ _ __ ___ _ _| | ___
|
|
||||||
/ __/ _` | '_ \/ __| | | | |/ _ \
|
|
||||||
| (_| (_| | |_) \__ | |_| | | __/_
|
|
||||||
\___\__,_| .__/|___/\__,_|_|\___(_)
|
|
||||||
| | | |
|
|
||||||
| |_ _____|_| ___ __
|
|
||||||
| __/ _ \ \ /\ / | '_ \
|
|
||||||
| || (_) \ V V /| | | |
|
|
||||||
\__\___/ \_/\_/ |_| |_|
|
|
||||||
```
|
|
||||||
|
|
||||||
# Welcome to capsule.town!
|
|
||||||
|
|
||||||
🌆 capsule.town is a safe and free place for hobbyists, gem-loggers, and everyone else to host their static Gemini capsules and gemlogs.
|
|
||||||
|
|
||||||
✨ No sign-up is required and your capsule can be live within seconds.
|
|
||||||
✨ You get a free Geminispace hosted at <yoursubdomain>.capsule.town.
|
|
||||||
✨ You can publish entirely anonymously.
|
|
||||||
|
|
||||||
🚧 This project is currently in open beta. Feedback, bug-reports, and contributions are welcome. Please see below for ways to get in touch.
|
|
||||||
|
|
||||||
=> gemini://capsule.town/start.gmi Get started
|
|
||||||
|
|
||||||
This project is open-source, and contributions are welcome. Likewise, you are free to run the code yourself to host your own Gemini constellation.
|
|
||||||
|
|
||||||
=> https://git.wilw.dev/wilw/capsule-town Project source
|
|
||||||
|
|
||||||
## Why host your content on capsule.town?
|
|
||||||
|
|
||||||
capsule.town is free and aims to offer a convenient space for people that would like a Geminispace but don't want to or can't host it themselves.
|
|
||||||
|
|
||||||
Think of this service as the Gemini version of static site hosting services like Netlify or Vercel. You can publish your capsule at any time using a simple client without needing to worry about provisioning and maintaining servers.
|
|
||||||
|
|
||||||
=> gemini://capsule.town/start.gmi Get started
|
|
||||||
|
|
||||||
## Why is it free?
|
|
||||||
|
|
||||||
capsule.town does not generate any income, and I pay for the servers and 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).
|
|
||||||
|
|
||||||
By taking part you are certainly not the product; you are a member of the community.
|
|
||||||
|
|
||||||
## What other options do I have?
|
|
||||||
|
|
||||||
We know that this type of service isn't for everybody. If you want a Geminispace but do not want to use capsule.town, there are other options available. For example:
|
|
||||||
|
|
||||||
=> gemini://gem.limpet.net/agate Host a space yourself using Agate
|
|
||||||
=> https://tildeverse.org Many `tildeverse` servers offer Gemini hosting
|
|
||||||
|
|
||||||
## Get in touch
|
|
||||||
|
|
||||||
Check out my own capsule for ways to get in touch:
|
|
||||||
|
|
||||||
=> gemini://wilw.capsule.town My Capsule
|
|
@ -1,27 +0,0 @@
|
|||||||
# capsule.town: Rules
|
|
||||||
|
|
||||||
=> gemini://capsule.town Return home
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Please note that capsule.town offers no warranty/SLA or anything else of this type. There is no legally binding agreement between the owners and the users of the service. It is a free service that aims to give back to the community, so treat is as such. The service or its owners are not liable for any loss of time/money/resource that may be incurred from use of the service or lack thereof.
|
|
||||||
|
|
||||||
The admins and owners are not responsible for the content generated and posted by its users.
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
capsule.town aims to be a safe space for everyone. Its purpose is to provide a space for people to host constructive, useful content, personal items, portfolios, and so on.
|
|
||||||
|
|
||||||
This is a space for free speech, where it is constructive. In general, the "don't be a jerk" rules apply. Also:
|
|
||||||
|
|
||||||
* Don't use the service in any way that may disrupt the underlying servers, or the experience for other people.
|
|
||||||
* Don't post hate-speech or content that aims to discriminate against individuals or groups of people based on their race, ethnicity, gender, sexuality, religion or other characteristics.
|
|
||||||
* Don't harass or bully others or post content that falsely defames someone or some entity (unless done in a constructive way).
|
|
||||||
* Don't post content or links to content that is/are illegal in the EU or the US.
|
|
||||||
|
|
||||||
The server admins reserve the right to remove any content without warning, and at their sole discretion.
|
|
||||||
|
|
||||||
=> gemini://capsule.town Return home
|
|
||||||
=> gemini://capsule.town/start.gmi Get started
|
|
@ -1,122 +0,0 @@
|
|||||||
# capsule.town: Getting started
|
|
||||||
|
|
||||||
=> gemini://capsule.town Return home
|
|
||||||
|
|
||||||
🚀 We're excited for you to join capsule.town!
|
|
||||||
|
|
||||||
🚧 Please note that this service is currently in early beta. Please get in touch if you have any feedback, bug reports, or would like to contribute.
|
|
||||||
|
|
||||||
## Intro
|
|
||||||
|
|
||||||
Publishing your capsule is handled through a HTTP API, however we recommend most people make use of our official wrapper client, which is described below.
|
|
||||||
|
|
||||||
🗒 Note: whilst we encourage use of the Gemini protocol for publishing and consuming text content, we do use HTTPS for our management API. The HTTP/S protocol offers the useful tooling required for such management, and it's more about using the right tool for the job 🙂
|
|
||||||
|
|
||||||
## Getting started guide
|
|
||||||
|
|
||||||
### 1. Download the client
|
|
||||||
|
|
||||||
We recommend most people download and make use of the capsule.town client, called `captown`, for publishing their capsule. However, if you'd rather use your own tooling (cURL, or something else) you can interface directly with the service using HTTPS (documentation for this is coming soon).
|
|
||||||
|
|
||||||
The current version of the client is: 0.1. Replace the version numbers in commands below to get the latest version.
|
|
||||||
|
|
||||||
You can use CURL to get the client, as described below.
|
|
||||||
|
|
||||||
For Linux:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ curl https://download.capsule.town/captown-linux-0.2 -o captown
|
|
||||||
```
|
|
||||||
|
|
||||||
For Mac:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ curl https://download.capsule.town/captown-mac-0.2 -o captown
|
|
||||||
```
|
|
||||||
|
|
||||||
ℹ️ We do not yet have a Windows version, but contributions are welcome.
|
|
||||||
|
|
||||||
Once downloaded:
|
|
||||||
|
|
||||||
1. Mark the binary as executable: `chmod +x captown`
|
|
||||||
2. Move the binary to a location of your choosing: e.g. `sudo mv captown /usr/local/bin`.
|
|
||||||
|
|
||||||
The rest of this guide assumes the binary is somewhere on your `PATH` and can be invoked using `captown` (as above).
|
|
||||||
|
|
||||||
🗒 Note: the source code of the binary can be examined. Please find a link to the source from the project homepage.
|
|
||||||
|
|
||||||
### 2. Prepare your capsule
|
|
||||||
|
|
||||||
You can now create a new capsule. To do so, create a new project directory (e.g. `~/projects/my_capsule`) and change into it:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ mkdir -p ~/projects/my_capsule
|
|
||||||
➡️ cd ~/projects/my_capsule
|
|
||||||
```
|
|
||||||
|
|
||||||
Now create a `capsule/` directory inside this project - this is where your Gemini files will be stored. The client looks for a `capsule/` directory when publishing your capsule:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ mkdir capsule
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a new file called `capsule/index.gmi` and write a welcome message inside:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ echo "# Hello, world" > capsule/index.gmi
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Create your capsule
|
|
||||||
|
|
||||||
By creating and publishing your capsule on capsule.town, you agree to the capsule.town rules.
|
|
||||||
|
|
||||||
=> gemini://capsule.town/rules.gmi Check out the rules
|
|
||||||
|
|
||||||
The next step is to create your new capsule on capsule.town. To do so, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ captown create
|
|
||||||
```
|
|
||||||
|
|
||||||
The client will ask for a few details:
|
|
||||||
|
|
||||||
* Name: the name of your capsule. This will be used as the subdomain, and will host your capsule at `<name>.capsule.town`. It must only contain letters and numbers and it must be unique.
|
|
||||||
* Contact details: _this is optional_. If you like, you can leave an email, Telegram handle, or another contact identifier. We only use this when verifying ownership in case you lose your access credentials.
|
|
||||||
|
|
||||||
Once done, your capsule will be created and the access key will be written to `~/.capsules.yaml`.
|
|
||||||
|
|
||||||
⚠️ **IMPORTANT: Please remember to backup this file (e.g. to your password manager). If you lose your access keys you will not be able to publish any new updates.**
|
|
||||||
|
|
||||||
### 4. Publish your capsule
|
|
||||||
|
|
||||||
Finally, you can publish your capsule.
|
|
||||||
|
|
||||||
Run `captown publish` inside your project directory. The program will ask for your capsule name to create the deployment.
|
|
||||||
|
|
||||||
Alternatively you can run the following to publish your capsule non-interactively:
|
|
||||||
|
|
||||||
```
|
|
||||||
➡️ captown publish -c mycapsule
|
|
||||||
```
|
|
||||||
|
|
||||||
Whichever route you choose, if all goes well you'll then be able to visit `gemini://<name>.capsule.town` using your Gemini client to view your new capsule.
|
|
||||||
|
|
||||||
🗒 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
|
|
||||||
|
|
||||||
When you've made updates that you want to publish, simply run `captown publish` again to make them live.
|
|
||||||
|
|
||||||
Side note: we recommend committing your capsule project directory to source control as you would for any other project.
|
|
||||||
|
|
||||||
=> gemini://capsule.town Return home
|
|
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();
|
||||||
@ -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