From e0298c993423ecb9b889196ea85b4cabc5b8d9d3 Mon Sep 17 00:00:00 2001 From: Overlord Date: Wed, 26 Nov 2025 19:51:26 +0100 Subject: [PATCH] eod commit, working beta deployed (https://dev.security-command.org/) --- app/Dockerfile | 33 +++++++++++++++++++++++ app/src/database/db.v | 31 +++++++++++++-------- app/src/database/tables.v | 41 ++++++++++++++-------------- app/src/main.v | 45 ++++++++++++++++++++++--------- app/src/template/assets/style.css | 2 +- app/src/util/structs.v | 26 +++++++++++++----- docker-compose.yml | 6 +---- 7 files changed, 128 insertions(+), 56 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index e69de29..d22dbca 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -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"] diff --git a/app/src/database/db.v b/app/src/database/db.v index 69b442c..a35a2df 100644 --- a/app/src/database/db.v +++ b/app/src/database/db.v @@ -1,8 +1,10 @@ module database +import os import rand +import util // import db.mysql -import db.redis +// import db.redis import fleximus.argon2 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}') } } +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 { // mut: // conn mysql.DB @@ -46,13 +55,13 @@ pub fn Crypto.hash_verify(password string, hash string) !bool { // // pub fn Database.test() {} -pub fn test() ! { - mut r := redis.connect(redis.Config{ - host: 'vpn.security-command.org' - port: 6767 - password: 'SuperSecretPassword123' - })! - - pong := r.ping() or { panic(err) } - println(pong) -} +// pub fn test() ! { +// mut r := redis.connect(redis.Config{ +// host: 'vpn.security-command.org' +// port: 6767 +// password: 'SuperSecretPassword123' +// })! +// +// pong := r.ping() or { panic(err) } +// println(pong) +// } diff --git a/app/src/database/tables.v b/app/src/database/tables.v index ddb0781..2f3e0d9 100644 --- a/app/src/database/tables.v +++ b/app/src/database/tables.v @@ -1,22 +1,23 @@ module database -@[table: 'users'] -struct Users { - id int @[primary; serial] - name string @[nonnull; unique] - password_hash string @[nonnull] -} - -@[table: 'login_attempts'] -struct Logins { - ip string @[primary] - attempts int @[nonnull] - attempt_time string @[default: 'CURRENT_TIMESTAMP'; nonnull] -} - -@[table: 'files'] -struct Files { - id int @[primary; serial] - path string @[nonnull; unique] - visible bool @[default: false; nonnull] -} +// +// @[table: 'users'] +// struct Users { +// id int @[primary; serial] +// name string @[nonnull; unique] +// password_hash string @[nonnull] +// } +// +// @[table: 'login_attempts'] +// struct Logins { +// ip string @[primary] +// attempts int @[nonnull] +// attempt_time string @[default: 'CURRENT_TIMESTAMP'; nonnull] +// } +// +// @[table: 'files'] +// struct Files { +// id int @[primary; serial] +// path string @[nonnull; unique] +// visible bool @[default: false; nonnull] +// } diff --git a/app/src/main.v b/app/src/main.v index 0359809..983971b 100644 --- a/app/src/main.v +++ b/app/src/main.v @@ -3,6 +3,7 @@ module main import os import veb import util +import database import thomaspeissl.dotenv // structs @@ -54,7 +55,8 @@ pub fn (app &App) root(mut ctx Context, path string) veb.Result { 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]) 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 { 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 { @@ -109,24 +111,37 @@ pub fn (mut ctx Context) server_error(msg string) veb.Result { // main -fn populate() (&util.Config, &util.Embedded) -{ +fn populate() (&util.Config, &util.Embedded) { dotenv.load() 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_user := $d('username', 'cdn') def_pass := $d('password', 'totallySafeCdnDatabasePassword1235') 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 } database: &util.Database{ host: os.getenv('CDN_DB_HOST').str() port: os.getenv('CDN_DB_PORT').int() - username: if os.getenv('CDN_DB_USERNAME') != '' { 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 } + username: if os.getenv('CDN_DB_USERNAME') != '' { + 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{ 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() - mut app := &App{ cfg: cfg, embed: embed } - mut auth := &Auth{ app: &app } + mut app := &App{ + cfg: cfg + embed: embed + } + mut auth := &Auth{ + app: &app + } app.register_controller[Auth, Context]('/auth', mut auth)! app.enable_static_compression = true app.use(veb.encode_auto[Context]()) - app.use(veb.MiddlewareOptions[Context] { + app.use(veb.MiddlewareOptions[Context]{ handler: fn [mut embed] (mut ctx Context) bool { ctx.embed = embed return true diff --git a/app/src/template/assets/style.css b/app/src/template/assets/style.css index 9d618d0..427bc12 100644 --- a/app/src/template/assets/style.css +++ b/app/src/template/assets/style.css @@ -63,7 +63,7 @@ body { .header { background-color: var(--bg-secondary); border-bottom: 1px solid var(--border-color); - padding: 3rem; + padding: 2rem 3rem 1rem 3rem; position: sticky; top: 0; z-index: 10; diff --git a/app/src/util/structs.v b/app/src/util/structs.v index d01437c..fcabb55 100644 --- a/app/src/util/structs.v +++ b/app/src/util/structs.v @@ -3,6 +3,7 @@ module util import os import time +import encoding.base64 pub struct Database { pub: @@ -14,6 +15,7 @@ pub: pub struct Config { pub: + exe string root string port int database Database @@ -71,13 +73,11 @@ pub fn Utility.get_icon(ext string, is_dir bool) string { 'unknown': '' } - return Utility.svg_to_data_uri(if is_dir { icons['dir'] } else { icons[ext] or { - icons['unknown']} }) + return if is_dir { icons['dir'] } else { icons[ext] or { icons['unknown'] } } } 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 { @@ -105,20 +105,34 @@ pub fn Utility.format_date(unix_time i64) string { 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{} files := os.ls(dir_path)! for file in files { 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) file_info := os.stat(full_path)! file_ext := full_path.split('.').last() entries << FileEntry{ 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' } is_dir: is_dir size: if !is_dir { file_info.size } else { 0 } diff --git a/docker-compose.yml b/docker-compose.yml index f9059ef..667c302 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,11 @@ services: app: - build: - context: ./app - dockerfile: Dockerfile container_name: cdn_web depends_on: - db ports: - - "8080:8080" + - "8080:8080" // how do these get passed to my exe?, etc environment: - # env shee DB_HOST: db DB_USER: appuser DB_PASS: s3cret