eod commit, working beta deployed (https://dev.security-command.org/)
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
FROM ghcr.io/prantlf/vlang:latest AS builder
|
||||||
|
WORKDIR ./src/
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN mkdir -p ./src/../../build/
|
||||||
|
|
||||||
|
# Build release binary. Output to /src/bin/cdn_app
|
||||||
|
# If your main file is main.v in the root of the context, this will work.
|
||||||
|
RUN v -prod -o ./src/../../build/ ./main.v
|
||||||
|
|
||||||
|
# Stage 2: runtime
|
||||||
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# create non-root user (good practice)
|
||||||
|
RUN useradd -m appuser
|
||||||
|
|
||||||
|
# copy binary from builder
|
||||||
|
COPY --from=builder /src/bin/cdn_app /app/cdn_app
|
||||||
|
|
||||||
|
# copy templates/assets if your binary loads embedded files at runtime or expects files on disk
|
||||||
|
# (If you use V's $embed_file at compile time then templates are already embedded and this is optional)
|
||||||
|
COPY template/ /app/template/
|
||||||
|
COPY util/ /app/util/
|
||||||
|
COPY database/ /app/database/
|
||||||
|
|
||||||
|
RUN chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# expose container ports your app might use (adjust if you use different ports)
|
||||||
|
EXPOSE 8080 6767
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/cdn_app"]
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
module database
|
module database
|
||||||
|
|
||||||
|
import os
|
||||||
import rand
|
import rand
|
||||||
|
import util
|
||||||
// import db.mysql
|
// import db.mysql
|
||||||
import db.redis
|
// import db.redis
|
||||||
import fleximus.argon2
|
import fleximus.argon2
|
||||||
|
|
||||||
pub struct Crypto {}
|
pub struct Crypto {}
|
||||||
@@ -17,6 +19,13 @@ pub fn Crypto.hash_verify(password string, hash string) !bool {
|
|||||||
return argon2.verify(hash, password.bytes()) or { return error('argon2 verify failed: ${err}') }
|
return argon2.verify(hash, password.bytes()) or { return error('argon2 verify failed: ${err}') }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_dummy_exclusion_list(exe string, root string) []string {
|
||||||
|
return [
|
||||||
|
exe,
|
||||||
|
util.Utility.normalize_path(os.join_path(root, 'update.sh')),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// struct Database {
|
// struct Database {
|
||||||
// mut:
|
// mut:
|
||||||
// conn mysql.DB
|
// conn mysql.DB
|
||||||
@@ -46,13 +55,13 @@ pub fn Crypto.hash_verify(password string, hash string) !bool {
|
|||||||
//
|
//
|
||||||
// pub fn Database.test() {}
|
// pub fn Database.test() {}
|
||||||
|
|
||||||
pub fn test() ! {
|
// pub fn test() ! {
|
||||||
mut r := redis.connect(redis.Config{
|
// mut r := redis.connect(redis.Config{
|
||||||
host: 'vpn.security-command.org'
|
// host: 'vpn.security-command.org'
|
||||||
port: 6767
|
// port: 6767
|
||||||
password: 'SuperSecretPassword123'
|
// password: 'SuperSecretPassword123'
|
||||||
})!
|
// })!
|
||||||
|
//
|
||||||
pong := r.ping() or { panic(err) }
|
// pong := r.ping() or { panic(err) }
|
||||||
println(pong)
|
// println(pong)
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
module database
|
module database
|
||||||
|
|
||||||
@[table: 'users']
|
//
|
||||||
struct Users {
|
// @[table: 'users']
|
||||||
id int @[primary; serial]
|
// struct Users {
|
||||||
name string @[nonnull; unique]
|
// id int @[primary; serial]
|
||||||
password_hash string @[nonnull]
|
// name string @[nonnull; unique]
|
||||||
}
|
// password_hash string @[nonnull]
|
||||||
|
// }
|
||||||
@[table: 'login_attempts']
|
//
|
||||||
struct Logins {
|
// @[table: 'login_attempts']
|
||||||
ip string @[primary]
|
// struct Logins {
|
||||||
attempts int @[nonnull]
|
// ip string @[primary]
|
||||||
attempt_time string @[default: 'CURRENT_TIMESTAMP'; nonnull]
|
// attempts int @[nonnull]
|
||||||
}
|
// attempt_time string @[default: 'CURRENT_TIMESTAMP'; nonnull]
|
||||||
|
// }
|
||||||
@[table: 'files']
|
//
|
||||||
struct Files {
|
// @[table: 'files']
|
||||||
id int @[primary; serial]
|
// struct Files {
|
||||||
path string @[nonnull; unique]
|
// id int @[primary; serial]
|
||||||
visible bool @[default: false; nonnull]
|
// path string @[nonnull; unique]
|
||||||
}
|
// visible bool @[default: false; nonnull]
|
||||||
|
// }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module main
|
|||||||
import os
|
import os
|
||||||
import veb
|
import veb
|
||||||
import util
|
import util
|
||||||
|
import database
|
||||||
import thomaspeissl.dotenv
|
import thomaspeissl.dotenv
|
||||||
|
|
||||||
// structs
|
// structs
|
||||||
@@ -54,7 +55,8 @@ pub fn (app &App) root(mut ctx Context, path string) veb.Result {
|
|||||||
return ctx.text(content)
|
return ctx.text(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries := util.Utility.list_files(abs_path, abs_root) or { return ctx.not_found() }
|
exclude := database.get_dummy_exclusion_list(app.cfg.exe, app.cfg.root)
|
||||||
|
entries := util.Utility.list_files(abs_path, abs_root, exclude) or { return ctx.not_found() }
|
||||||
|
|
||||||
directory := util.HtmlBuilder.generate_breadcrumbs(abs_path.split(abs_root)[1])
|
directory := util.HtmlBuilder.generate_breadcrumbs(abs_path.split(abs_root)[1])
|
||||||
files, meta := util.HtmlBuilder.generate_file_list(entries, abs_path)
|
files, meta := util.HtmlBuilder.generate_file_list(entries, abs_path)
|
||||||
@@ -94,7 +96,7 @@ pub fn (mut ctx Context) request_error(msg string) veb.Result {
|
|||||||
|
|
||||||
pub fn (mut ctx Context) forbidden() veb.Result {
|
pub fn (mut ctx Context) forbidden() veb.Result {
|
||||||
ctx.res.set_status(.forbidden)
|
ctx.res.set_status(.forbidden)
|
||||||
return ctx.html(ctx.error_page(403, 'Forbidden', 'Oops! You aren\'t allowed around here.'))
|
return ctx.html(ctx.error_page(403, 'Forbidden', "Oops! You aren't allowed around here."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut ctx Context) not_found() veb.Result {
|
pub fn (mut ctx Context) not_found() veb.Result {
|
||||||
@@ -109,24 +111,37 @@ pub fn (mut ctx Context) server_error(msg string) veb.Result {
|
|||||||
|
|
||||||
// main
|
// main
|
||||||
|
|
||||||
fn populate() (&util.Config, &util.Embedded)
|
fn populate() (&util.Config, &util.Embedded) {
|
||||||
{
|
|
||||||
dotenv.load()
|
dotenv.load()
|
||||||
dotenv.require('CDN_DB_HOST', 'CDN_DB_PORT')
|
dotenv.require('CDN_DB_HOST', 'CDN_DB_PORT')
|
||||||
|
|
||||||
def_root := $d('root', '.')
|
executable := os.executable()
|
||||||
|
def_root := os.dir(executable)
|
||||||
def_port := int($d('port', 6767))
|
def_port := int($d('port', 6767))
|
||||||
def_user := $d('username', 'cdn')
|
def_user := $d('username', 'cdn')
|
||||||
def_pass := $d('password', 'totallySafeCdnDatabasePassword1235')
|
def_pass := $d('password', 'totallySafeCdnDatabasePassword1235')
|
||||||
|
|
||||||
return &util.Config{
|
return &util.Config{
|
||||||
root: if os.getenv('CDN_ROOT') != '' { os.getenv('CDN_ROOT').str() } else { def_root }
|
exe: util.Utility.normalize_path(executable)
|
||||||
|
root: util.Utility.normalize_path(if os.getenv('CDN_ROOT') != '' {
|
||||||
|
util.Utility.resolve_path(def_root, os.getenv('CDN_ROOT').str())
|
||||||
|
} else {
|
||||||
|
def_root
|
||||||
|
})
|
||||||
port: if os.getenv('CDN_PORT') != '' { os.getenv('CDN_PORT').int() } else { def_port }
|
port: if os.getenv('CDN_PORT') != '' { os.getenv('CDN_PORT').int() } else { def_port }
|
||||||
database: &util.Database{
|
database: &util.Database{
|
||||||
host: os.getenv('CDN_DB_HOST').str()
|
host: os.getenv('CDN_DB_HOST').str()
|
||||||
port: os.getenv('CDN_DB_PORT').int()
|
port: os.getenv('CDN_DB_PORT').int()
|
||||||
username: if os.getenv('CDN_DB_USERNAME') != '' { os.getenv('CDN_DB_USERNAME').str() } else { def_user }
|
username: if os.getenv('CDN_DB_USERNAME') != '' {
|
||||||
password: if os.getenv('CDN_DB_PASSWORD') != '' { os.getenv('CDN_DB_PASSWORD').str() } else { def_pass }
|
os.getenv('CDN_DB_USERNAME').str()
|
||||||
|
} else {
|
||||||
|
def_user
|
||||||
|
}
|
||||||
|
password: if os.getenv('CDN_DB_PASSWORD') != '' {
|
||||||
|
os.getenv('CDN_DB_PASSWORD').str()
|
||||||
|
} else {
|
||||||
|
def_pass
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, &util.Embedded{
|
}, &util.Embedded{
|
||||||
style_css: $embed_file('template/assets/style.css', .zlib).to_string()
|
style_css: $embed_file('template/assets/style.css', .zlib).to_string()
|
||||||
@@ -134,18 +149,22 @@ fn populate() (&util.Config, &util.Embedded)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main()
|
fn main() {
|
||||||
{
|
|
||||||
cfg, mut embed := populate()
|
cfg, mut embed := populate()
|
||||||
mut app := &App{ cfg: cfg, embed: embed }
|
mut app := &App{
|
||||||
mut auth := &Auth{ app: &app }
|
cfg: cfg
|
||||||
|
embed: embed
|
||||||
|
}
|
||||||
|
mut auth := &Auth{
|
||||||
|
app: &app
|
||||||
|
}
|
||||||
|
|
||||||
app.register_controller[Auth, Context]('/auth', mut auth)!
|
app.register_controller[Auth, Context]('/auth', mut auth)!
|
||||||
|
|
||||||
app.enable_static_compression = true
|
app.enable_static_compression = true
|
||||||
app.use(veb.encode_auto[Context]())
|
app.use(veb.encode_auto[Context]())
|
||||||
|
|
||||||
app.use(veb.MiddlewareOptions[Context] {
|
app.use(veb.MiddlewareOptions[Context]{
|
||||||
handler: fn [mut embed] (mut ctx Context) bool {
|
handler: fn [mut embed] (mut ctx Context) bool {
|
||||||
ctx.embed = embed
|
ctx.embed = embed
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ body {
|
|||||||
.header {
|
.header {
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
padding: 3rem;
|
padding: 2rem 3rem 1rem 3rem;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module util
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import encoding.base64
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
pub:
|
pub:
|
||||||
@@ -14,6 +15,7 @@ pub:
|
|||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub:
|
pub:
|
||||||
|
exe string
|
||||||
root string
|
root string
|
||||||
port int
|
port int
|
||||||
database Database
|
database Database
|
||||||
@@ -71,13 +73,11 @@ pub fn Utility.get_icon(ext string, is_dir bool) string {
|
|||||||
'unknown': '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>'
|
'unknown': '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>'
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utility.svg_to_data_uri(if is_dir { icons['dir'] } else { icons[ext] or {
|
return if is_dir { icons['dir'] } else { icons[ext] or { icons['unknown'] } }
|
||||||
icons['unknown']} })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Utility.svg_to_data_uri(svg string) string {
|
pub fn Utility.svg_to_data_uri(svg string) string {
|
||||||
return svg
|
return 'data:image/svg+xml;base64,${base64.encode(svg.bytes())}'
|
||||||
// return 'data:image/svg+xml;base64,${base64.encode(svg.bytes())}'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Utility.format_size(bytes i64) string {
|
pub fn Utility.format_size(bytes i64) string {
|
||||||
@@ -105,20 +105,34 @@ pub fn Utility.format_date(unix_time i64) string {
|
|||||||
return time.unix(unix_time).format_ss_milli()
|
return time.unix(unix_time).format_ss_milli()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Utility.list_files(dir_path string, relative string) ![]FileEntry {
|
pub fn Utility.resolve_path(base string, path string) string {
|
||||||
|
if os.is_abs_path(path) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.real_path(os.join_path(base, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Utility.list_files(dir_path string, relative string, exclude []string) ![]FileEntry {
|
||||||
mut entries := []FileEntry{}
|
mut entries := []FileEntry{}
|
||||||
|
|
||||||
files := os.ls(dir_path)!
|
files := os.ls(dir_path)!
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
full_path := os.join_path(dir_path, file)
|
full_path := os.join_path(dir_path, file)
|
||||||
|
normalized := Utility.normalize_path(full_path)
|
||||||
|
|
||||||
|
if normalized in exclude {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
is_dir := os.is_dir(full_path)
|
is_dir := os.is_dir(full_path)
|
||||||
file_info := os.stat(full_path)!
|
file_info := os.stat(full_path)!
|
||||||
file_ext := full_path.split('.').last()
|
file_ext := full_path.split('.').last()
|
||||||
|
|
||||||
entries << FileEntry{
|
entries << FileEntry{
|
||||||
name: if is_dir { '${file}/' } else { file }
|
name: if is_dir { '${file}/' } else { file }
|
||||||
abs_path: Utility.normalize_path(full_path)
|
abs_path: normalized
|
||||||
path: Utility.normalize_path(full_path).split(relative)[1] or { 'err' }
|
path: Utility.normalize_path(full_path).split(relative)[1] or { 'err' }
|
||||||
is_dir: is_dir
|
is_dir: is_dir
|
||||||
size: if !is_dir { file_info.size } else { 0 }
|
size: if !is_dir { file_info.size } else { 0 }
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
|
||||||
context: ./app
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: cdn_web
|
container_name: cdn_web
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080" // how do these get passed to my exe?, etc
|
||||||
environment:
|
environment:
|
||||||
# env shee
|
|
||||||
DB_HOST: db
|
DB_HOST: db
|
||||||
DB_USER: appuser
|
DB_USER: appuser
|
||||||
DB_PASS: s3cret
|
DB_PASS: s3cret
|
||||||
|
|||||||
Reference in New Issue
Block a user