mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2025-09-04 08:30:39 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
53c7c9d9e2 | |||
df9c60c29e | |||
f29b6b23f0 | |||
cc88fa6763 | |||
19022e7cb5 | |||
44f43dbc2c | |||
45f6f3af32 | |||
9bd544f0d5 | |||
a315e58284 | |||
d576b71bc5 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -5,7 +5,25 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [1.4.0] - 2022-01-16
|
## [1.5.1] - 2022-05-15
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Remove double note content
|
||||||
|
|
||||||
|
## [1.5.0] - 2022-05-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Links in notes are not highlighted and can be directly clicked #30.
|
||||||
|
|
||||||
|
## [1.4.1] - 2022-03-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Router in prod build.
|
||||||
|
|
||||||
|
## [1.4.0] - 2022-03-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
43
README.md
43
README.md
@@ -48,10 +48,13 @@ of the notes even if it tried to.
|
|||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
| ------------ | ----------------- | --------------------------------------------------------------------------------------- |
|
| ---------------- | ----------------- | --------------------------------------------------------------------------------------- |
|
||||||
| `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. |
|
| `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. |
|
||||||
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) |
|
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) |
|
||||||
|
| `MAX_VIEWS` | `100` | Maximal number of views. |
|
||||||
|
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
|
||||||
|
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
@@ -124,11 +127,35 @@ services:
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
1. Clone
|
**Requirements**
|
||||||
2. run `pnpm i` in the root and and client `client/` folders.
|
|
||||||
3. Run `pnpm run dev` to start development.
|
|
||||||
|
|
||||||
Running `npm run dev` in the root folder will start the following things
|
- `pnpm`: `>=6`
|
||||||
|
- `node`: `>=14`
|
||||||
|
- `rust`: edition `2021`
|
||||||
|
|
||||||
|
**Install**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm --prefix frontend install
|
||||||
|
|
||||||
|
# Also you need cargo watch if you don't already have it installed.
|
||||||
|
# https://lib.rs/crates/cargo-watch
|
||||||
|
cargo install cargo-watch
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run**
|
||||||
|
|
||||||
|
Make sure you have docker running.
|
||||||
|
|
||||||
|
> If you are on `macOS` you might need to disable AirPlay Receiver as it uses port 5000 (So stupid...)
|
||||||
|
> https://developer.apple.com/forums/thread/682332
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Running `pnpm run dev` in the root folder will start the following things:
|
||||||
|
|
||||||
- a memcache docker container
|
- a memcache docker container
|
||||||
- rust backend with hot reload
|
- rust backend with hot reload
|
||||||
|
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -398,7 +398,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "1.4.0"
|
version = "1.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "1.4.0"
|
version = "1.5.1"
|
||||||
authors = ["cupcakearmy <hi@nicco.io>"]
|
authors = ["cupcakearmy <hi@nicco.io>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@@ -4,9 +4,9 @@ use crate::note;
|
|||||||
use crate::status;
|
use crate::status;
|
||||||
|
|
||||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("/api")
|
web::scope("/api")
|
||||||
.service(note::init())
|
.service(note::init())
|
||||||
.service(status::init()),
|
.service(status::init()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
use actix_files::Files;
|
use actix_files::{Files, NamedFile};
|
||||||
use actix_web::web;
|
use actix_web::{web, Result};
|
||||||
|
|
||||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
Files::new("/", "./frontend/build")
|
Files::new("/", "./frontend/build")
|
||||||
.index_file("index.html")
|
.index_file("index.html")
|
||||||
.use_etag(true),
|
.use_etag(true),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn index() -> Result<NamedFile> {
|
||||||
|
Ok(NamedFile::open("./frontend/build/index.html")?)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use actix_web::{middleware, App, HttpServer};
|
use actix_web::{middleware, web, App, HttpServer};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -22,6 +22,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.configure(size::init)
|
.configure(size::init)
|
||||||
.configure(api::init)
|
.configure(api::init)
|
||||||
.configure(client::init)
|
.configure(client::init)
|
||||||
|
.default_service(web::to(client::index))
|
||||||
})
|
})
|
||||||
.bind("0.0.0.0:5000")?
|
.bind("0.0.0.0:5000")?
|
||||||
.run()
|
.run()
|
||||||
|
@@ -4,10 +4,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
pub views: Option<u32>,
|
pub views: Option<u32>,
|
||||||
pub expiration: Option<u32>,
|
pub expiration: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@@ -15,13 +15,13 @@ pub struct NoteInfo {}
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NotePublic {
|
pub struct NotePublic {
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_id() -> String {
|
pub fn generate_id() -> String {
|
||||||
let mut id: [u8; 32] = [0; 32];
|
let mut id: [u8; 32] = [0; 32];
|
||||||
let sr = ring::rand::SystemRandom::new();
|
let sr = ring::rand::SystemRandom::new();
|
||||||
let _ = sr.fill(&mut id);
|
let _ = sr.fill(&mut id);
|
||||||
return bs62::encode_data(&id);
|
return bs62::encode_data(&id);
|
||||||
}
|
}
|
||||||
|
@@ -7,118 +7,118 @@ use crate::note::{generate_id, Note, NoteInfo, NotePublic};
|
|||||||
use crate::store;
|
use crate::store;
|
||||||
|
|
||||||
pub fn now() -> u32 {
|
pub fn now() -> u32 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as u32
|
.as_secs() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct NotePath {
|
struct NotePath {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{id}")]
|
#[get("/{id}")]
|
||||||
async fn one(path: web::Path<NotePath>) -> impl Responder {
|
async fn one(path: web::Path<NotePath>) -> impl Responder {
|
||||||
let p = path.into_inner();
|
let p = path.into_inner();
|
||||||
let note = store::get(&p.id);
|
let note = store::get(&p.id);
|
||||||
match note {
|
match note {
|
||||||
None => return HttpResponse::NotFound().finish(),
|
None => return HttpResponse::NotFound().finish(),
|
||||||
Some(_) => return HttpResponse::Ok().json(NoteInfo {}),
|
Some(_) => return HttpResponse::Ok().json(NoteInfo {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct CreateResponse {
|
struct CreateResponse {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/")]
|
#[post("/")]
|
||||||
async fn create(note: web::Json<Note>) -> impl Responder {
|
async fn create(note: web::Json<Note>) -> impl Responder {
|
||||||
let mut n = note.into_inner();
|
let mut n = note.into_inner();
|
||||||
let id = generate_id();
|
let id = generate_id();
|
||||||
let bad_req = HttpResponse::BadRequest().finish();
|
let bad_req = HttpResponse::BadRequest().finish();
|
||||||
if n.views == None && n.expiration == None {
|
if n.views == None && n.expiration == None {
|
||||||
return bad_req;
|
|
||||||
}
|
|
||||||
if !*config::ALLOW_ADVANCED {
|
|
||||||
n.views = Some(1);
|
|
||||||
n.expiration = None;
|
|
||||||
}
|
|
||||||
match n.views {
|
|
||||||
Some(v) => {
|
|
||||||
if v > *config::MAX_VIEWS {
|
|
||||||
return bad_req;
|
return bad_req;
|
||||||
}
|
|
||||||
n.expiration = None; // views overrides expiration
|
|
||||||
}
|
}
|
||||||
_ => {}
|
if !*config::ALLOW_ADVANCED {
|
||||||
}
|
n.views = Some(1);
|
||||||
match n.expiration {
|
n.expiration = None;
|
||||||
Some(e) => {
|
|
||||||
if e > *config::MAX_EXPIRATION {
|
|
||||||
return bad_req;
|
|
||||||
}
|
|
||||||
let expiration = now() + (e * 60);
|
|
||||||
n.expiration = Some(expiration);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
match n.views {
|
||||||
}
|
Some(v) => {
|
||||||
store::set(&id.clone(), &n.clone());
|
if v > *config::MAX_VIEWS {
|
||||||
return HttpResponse::Ok().json(CreateResponse { id: id });
|
return bad_req;
|
||||||
|
}
|
||||||
|
n.expiration = None; // views overrides expiration
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match n.expiration {
|
||||||
|
Some(e) => {
|
||||||
|
if e > *config::MAX_EXPIRATION {
|
||||||
|
return bad_req;
|
||||||
|
}
|
||||||
|
let expiration = now() + (e * 60);
|
||||||
|
n.expiration = Some(expiration);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
store::set(&id.clone(), &n.clone());
|
||||||
|
return HttpResponse::Ok().json(CreateResponse { id: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/{id}")]
|
#[delete("/{id}")]
|
||||||
async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
||||||
let p = path.into_inner();
|
let p = path.into_inner();
|
||||||
let note = store::get(&p.id);
|
let note = store::get(&p.id);
|
||||||
match note {
|
match note {
|
||||||
None => return HttpResponse::NotFound().finish(),
|
None => return HttpResponse::NotFound().finish(),
|
||||||
Some(note) => {
|
Some(note) => {
|
||||||
let mut changed = note.clone();
|
let mut changed = note.clone();
|
||||||
if changed.views == None && changed.expiration == None {
|
if changed.views == None && changed.expiration == None {
|
||||||
return HttpResponse::BadRequest().finish();
|
return HttpResponse::BadRequest().finish();
|
||||||
}
|
}
|
||||||
match changed.views {
|
match changed.views {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
changed.views = Some(v - 1);
|
changed.views = Some(v - 1);
|
||||||
let id = p.id.clone();
|
let id = p.id.clone();
|
||||||
if v <= 1 {
|
if v <= 1 {
|
||||||
store::del(&id);
|
store::del(&id);
|
||||||
} else {
|
} else {
|
||||||
store::set(&id, &changed.clone());
|
store::set(&id, &changed.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = now();
|
let n = now();
|
||||||
match changed.expiration {
|
match changed.expiration {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
if e < n {
|
if e < n {
|
||||||
store::del(&p.id.clone());
|
store::del(&p.id.clone());
|
||||||
return HttpResponse::BadRequest().finish();
|
return HttpResponse::BadRequest().finish();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return HttpResponse::Ok().json(NotePublic {
|
||||||
|
contents: changed.contents,
|
||||||
|
meta: changed.meta,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return HttpResponse::Ok().json(NotePublic {
|
|
||||||
contents: changed.contents,
|
|
||||||
meta: changed.meta,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Status {
|
struct Status {
|
||||||
version: String,
|
version: String,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Scope {
|
pub fn init() -> Scope {
|
||||||
web::scope("/notes")
|
web::scope("/notes")
|
||||||
.service(one)
|
.service(one)
|
||||||
.service(create)
|
.service(create)
|
||||||
.service(delete)
|
.service(delete)
|
||||||
}
|
}
|
||||||
|
@@ -3,16 +3,16 @@ use byte_unit::Byte;
|
|||||||
use mime;
|
use mime;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref LIMIT: usize =
|
pub static ref LIMIT: usize =
|
||||||
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
|
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_bytes() as usize;
|
.get_bytes() as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||||
let json = web::JsonConfig::default().limit(*LIMIT);
|
let json = web::JsonConfig::default().limit(*LIMIT);
|
||||||
let plain = web::PayloadConfig::default()
|
let plain = web::PayloadConfig::default()
|
||||||
.limit(*LIMIT)
|
.limit(*LIMIT)
|
||||||
.mimetype(mime::STAR_STAR);
|
.mimetype(mime::STAR_STAR);
|
||||||
cfg.app_data(json).app_data(plain);
|
cfg.app_data(json).app_data(plain);
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub max_size: u32,
|
pub max_size: u32,
|
||||||
pub max_views: u32,
|
pub max_views: u32,
|
||||||
pub max_expiration: u32,
|
pub max_expiration: u32,
|
||||||
pub allow_advanced: bool,
|
pub allow_advanced: bool,
|
||||||
}
|
}
|
||||||
|
@@ -5,15 +5,15 @@ use crate::status::Status;
|
|||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn get_status() -> impl Responder {
|
async fn get_status() -> impl Responder {
|
||||||
return HttpResponse::Ok().json(Status {
|
return HttpResponse::Ok().json(Status {
|
||||||
version: config::VERSION.to_string(),
|
version: config::VERSION.to_string(),
|
||||||
max_size: *config::LIMIT,
|
max_size: *config::LIMIT,
|
||||||
max_views: *config::MAX_VIEWS,
|
max_views: *config::MAX_VIEWS,
|
||||||
max_expiration: *config::MAX_EXPIRATION,
|
max_expiration: *config::MAX_EXPIRATION,
|
||||||
allow_advanced: *config::ALLOW_ADVANCED,
|
allow_advanced: *config::ALLOW_ADVANCED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Scope {
|
pub fn init() -> Scope {
|
||||||
web::scope("/status").service(get_status)
|
web::scope("/status").service(get_status)
|
||||||
}
|
}
|
||||||
|
@@ -4,33 +4,33 @@ use crate::note::now;
|
|||||||
use crate::note::Note;
|
use crate::note::Note;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CLIENT: memcache::Client = memcache::connect(format!(
|
static ref CLIENT: memcache::Client = memcache::connect(format!(
|
||||||
"memcache://{}?timeout=10&tcp_nodelay=true",
|
"memcache://{}?timeout=10&tcp_nodelay=true",
|
||||||
std::env::var("MEMCACHE").unwrap_or("127.0.0.1:11211".to_string())
|
std::env::var("MEMCACHE").unwrap_or("127.0.0.1:11211".to_string())
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(id: &String, note: &Note) {
|
pub fn set(id: &String, note: &Note) {
|
||||||
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
||||||
let expiration: u32 = match note.expiration {
|
let expiration: u32 = match note.expiration {
|
||||||
Some(e) => e - now(),
|
Some(e) => e - now(),
|
||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
CLIENT.set(id, serialized, expiration).unwrap();
|
CLIENT.set(id, serialized, expiration).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(id: &String) -> Option<Note> {
|
pub fn get(id: &String) -> Option<Note> {
|
||||||
let value: Option<String> = CLIENT.get(&id).unwrap();
|
let value: Option<String> = CLIENT.get(&id).unwrap();
|
||||||
match value {
|
match value {
|
||||||
None => return None,
|
None => return None,
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
let deserialize: Note = serde_json::from_str(&s).unwrap();
|
let deserialize: Note = serde_json::from_str(&s).unwrap();
|
||||||
return Some(deserialize);
|
return Some(deserialize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn del(id: &String) {
|
pub fn del(id: &String) {
|
||||||
CLIENT.delete(id).unwrap();
|
CLIENT.delete(id).unwrap();
|
||||||
}
|
}
|
||||||
|
161
examples/scratch/README.md
Normal file
161
examples/scratch/README.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Install from scratch.
|
||||||
|
|
||||||
|
This is a tiny guide to install cryptgeon on (probably) any unix system (and maybe windows?) from scratch using traefik as the proxy, which will manage certificates and handle https for us.
|
||||||
|
|
||||||
|
1. Install Docker & Docker Compose.
|
||||||
|
2. Install Traefik.
|
||||||
|
3. Run the cryptgeon.
|
||||||
|
4. [Optional] install watchtower to keep up to date.
|
||||||
|
|
||||||
|
## Install Docker & DOcker Compose
|
||||||
|
|
||||||
|
- [Docker](https://docs.docker.com/engine/install/)
|
||||||
|
- [Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
## Install Traefik 2.0
|
||||||
|
|
||||||
|
[Traefik](https://doc.traefik.io/traefik/) is a router & proxy that makes deployment of containers incredibly easy. It will manage all the https certificates, routing, etc.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
/foo/bar/traefik/
|
||||||
|
├── docker-compose.yaml
|
||||||
|
└── traefik.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yaml
|
||||||
|
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:2.6
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '443:443'
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./traefik.yaml:/etc/traefik/traefik.yaml:ro
|
||||||
|
- ./data:/data
|
||||||
|
labels:
|
||||||
|
- 'traefik.enable=true'
|
||||||
|
|
||||||
|
# HTTP to HTTPS redirection
|
||||||
|
- 'traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)'
|
||||||
|
- 'traefik.http.routers.http_catchall.entrypoints=insecure'
|
||||||
|
- 'traefik.http.routers.http_catchall.middlewares=https_redirect'
|
||||||
|
- 'traefik.http.middlewares.https_redirect.redirectscheme.scheme=https'
|
||||||
|
- 'traefik.http.middlewares.https_redirect.redirectscheme.permanent=true'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
external: true
|
||||||
|
name: proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# traefik.yaml
|
||||||
|
|
||||||
|
api:
|
||||||
|
dashboard: true
|
||||||
|
|
||||||
|
# Define HTTP and HTTPS entrypoint
|
||||||
|
entryPoints:
|
||||||
|
insecure:
|
||||||
|
address: ':80'
|
||||||
|
secure:
|
||||||
|
address: ':443'
|
||||||
|
|
||||||
|
# Dynamic configuration will come from docker labels
|
||||||
|
providers:
|
||||||
|
docker:
|
||||||
|
endpoint: 'unix:///var/run/docker.sock'
|
||||||
|
network: 'proxy'
|
||||||
|
exposedByDefault: false
|
||||||
|
|
||||||
|
# Enable acme with http file challenge
|
||||||
|
certificatesResolvers:
|
||||||
|
le:
|
||||||
|
acme:
|
||||||
|
email: me@example.org
|
||||||
|
storage: /data/acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: insecure
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker network create proxy
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cryptgeon
|
||||||
|
|
||||||
|
Create another docker-compose.yaml file in another folder. We will assume that the domain is `cryptgeon.example.org`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
/foo/bar/cryptgeon/
|
||||||
|
└── docker-compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
memcached:
|
||||||
|
image: memcached:1-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
entrypoint: memcached -m 256M -I 4M # Limit to 128 MB Ram, customize at free will.
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: cupcakearmy/cryptgeon:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- memcached
|
||||||
|
environment:
|
||||||
|
SIZE_LIMIT: 4 MiB
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.cryptgeon.rule=Host(`cryptgeon.example.org`)
|
||||||
|
- traefik.http.routers.cryptgeon.entrypoints=secure
|
||||||
|
- traefik.http.routers.cryptgeon.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Watchtower
|
||||||
|
|
||||||
|
> A container-based solution for automating Docker container base image updates.
|
||||||
|
|
||||||
|
[Watchtower](https://containrrr.dev/watchtower/) will keep our containers up to date. The interval is set to once a day and also configured to delete old images to prevent cluttering.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
/foo/bar/watchtower/
|
||||||
|
└── docker-compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yaml
|
||||||
|
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
watchtower:
|
||||||
|
image: containrrr/watchtower
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
command: --cleanup --interval 86400
|
||||||
|
```
|
@@ -11,23 +11,25 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lokalise/node-api": "^7.1.1",
|
"@lokalise/node-api": "^7.1.1",
|
||||||
"@sveltejs/adapter-static": "^1.0.0-next.26",
|
"@sveltejs/adapter-static": "^1.0.0-next.28",
|
||||||
"@sveltejs/kit": "^1.0.0-next.231",
|
"@sveltejs/kit": "1.0.0-next.288",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
|
"@types/sanitize-html": "^2.6.2",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"svelte": "^3.46.2",
|
"svelte": "^3.46.4",
|
||||||
"svelte-check": "^2.4.5",
|
"svelte-check": "^2.4.5",
|
||||||
"svelte-intl-precompile": "^0.8.0",
|
"svelte-intl-precompile": "^0.8.1",
|
||||||
"svelte-preprocess": "^4.10.1",
|
"svelte-preprocess": "^4.10.4",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.6.2",
|
||||||
"vite": "^2.7.12"
|
"vite": "^2.8.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/fira-mono": "^4.5.0",
|
"@fontsource/fira-mono": "^4.5.3",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"pretty-bytes": "^5.6.0"
|
"pretty-bytes": "^5.6.0",
|
||||||
|
"sanitize-html": "^2.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
579
frontend/pnpm-lock.yaml
generated
579
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,11 +4,13 @@
|
|||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import sanitize from 'sanitize-html'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import Button from './Button.svelte'
|
import Button from './Button.svelte'
|
||||||
|
|
||||||
export let note: NotePublic
|
export let note: NotePublic
|
||||||
|
|
||||||
|
const RE_URL = /[A-Za-z]+:\/\/([A-Z a-z0-9\-._~:\/?#\[\]@!$&'()*+,;%=])+/g
|
||||||
let files: FileDTO[] = []
|
let files: FileDTO[] = []
|
||||||
|
|
||||||
$: if (note.meta.type === 'file') {
|
$: if (note.meta.type === 'file') {
|
||||||
@@ -27,12 +29,20 @@
|
|||||||
})
|
})
|
||||||
saveAs(f)
|
saveAs(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function contentWithLinks(content: string): string {
|
||||||
|
const replaced = note.contents.replace(
|
||||||
|
RE_URL,
|
||||||
|
(url) => `<a href="${url}" rel="noreferrer">${url}</a>`
|
||||||
|
)
|
||||||
|
return sanitize(replaced, { allowedTags: ['a'], allowedAttributes: { a: ['href', 'rel'] } })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
|
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
|
||||||
{#if note.meta.type === 'text'}
|
{#if note.meta.type === 'text'}
|
||||||
<div class="note">
|
<div class="note">
|
||||||
{note.contents}
|
{@html contentWithLinks(note.contents)}
|
||||||
</div>
|
</div>
|
||||||
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
|
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
|
||||||
{:else}
|
{:else}
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
export const hydrate = dev
|
export const hydrate = dev
|
||||||
export const router = browser
|
export const router = browser
|
||||||
export const prerender = true
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@@ -1,7 +1,3 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Create from '$lib/views/Create.svelte'
|
import Create from '$lib/views/Create.svelte'
|
||||||
</script>
|
</script>
|
||||||
|
@@ -9,7 +9,6 @@ export default {
|
|||||||
adapter: adapter({
|
adapter: adapter({
|
||||||
fallback: 'index.html',
|
fallback: 'index.html',
|
||||||
}),
|
}),
|
||||||
target: '#svelte',
|
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
precompileIntl('locales'), // if your translations are defined in /locales/[lang].json
|
precompileIntl('locales'), // if your translations are defined in /locales/[lang].json
|
||||||
|
Reference in New Issue
Block a user