forked from vieter-v/vieter
				
			Wrote a proper env file system
							parent
							
								
									d6e71e9a1c
								
							
						
					
					
						commit
						1d434db166
					
				|  | @ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
|     * Packages are always rebuilt, even if they haven't changed | ||||
|     * Hardcoded planning of builds | ||||
|     * Builds are sequential | ||||
| * Better environment variable support | ||||
|     * Each env var can now be provided from a file by appending it with `_FILE` | ||||
|       & passing the path to the file as value | ||||
| 
 | ||||
| ## Fixed | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										19
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										19
									
								
								Makefile
								
								
								
								
							|  | @ -34,16 +34,21 @@ c: | |||
| # Run the server in the default 'data' directory
 | ||||
| .PHONY: run | ||||
| run: vieter | ||||
| 	 API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG ./vieter server | ||||
| 	 VIETER_API_KEY=test \
 | ||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 | ||||
| 		VIETER_REPO_DIR=data/repo \
 | ||||
| 		VIETER_PKG_DIR=data/pkgs \
 | ||||
| 		VIETER_LOG_LEVEL=DEBUG \
 | ||||
| 		./vieter server | ||||
| 
 | ||||
| .PHONY: run-prod | ||||
| run-prod: prod | ||||
| 	API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG ./pvieter | ||||
| 
 | ||||
| # Same as run, but restart when the source code changes
 | ||||
| .PHONY: watch | ||||
| watch: | ||||
| 	API_KEY=test DOWNLOAD_DIR=data/downloads REPO_DIR=data/repo PKG_DIR=data/pkgs LOG_LEVEL=DEBUG $(V) watch run vieter | ||||
| 	VIETER_API_KEY=test \
 | ||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 | ||||
| 		VIETER_REPO_DIR=data/repo \
 | ||||
| 		VIETER_PKG_DIR=data/pkgs \
 | ||||
| 		VIETER_LOG_LEVEL=DEBUG \
 | ||||
| 	./pvieter server | ||||
| 
 | ||||
| # =====OTHER=====
 | ||||
| .PHONY: lint | ||||
|  |  | |||
|  | @ -7,5 +7,5 @@ fn (mut app App) is_authorized() bool { | |||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	return x_header.trim_space() == app.api_key | ||||
| 	return x_header.trim_space() == app.conf.api_key | ||||
| } | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/build.v
								
								
								
								
							
							
						
						
									
										13
									
								
								src/build.v
								
								
								
								
							|  | @ -7,16 +7,15 @@ import time | |||
| import os | ||||
| import json | ||||
| import git | ||||
| import env | ||||
| 
 | ||||
| const container_build_dir = '/build' | ||||
| 
 | ||||
| fn build(key string, repo_dir string) ? { | ||||
| 	server_url := os.getenv_opt('VIETER_ADDRESS') or { | ||||
| 		exit_with_message(1, 'No Vieter server address was provided.') | ||||
| 	} | ||||
| fn build() ? { | ||||
| 	conf := env.load<env.BuildConfig>() ? | ||||
| 
 | ||||
| 	// Read in the repos from a json file | ||||
| 	filename := os.join_path_single(repo_dir, 'repos.json') | ||||
| 	filename := os.join_path_single(conf.repo_dir, 'repos.json') | ||||
| 	txt := os.read_file(filename) ? | ||||
| 	repos := json.decode([]git.GitRepo, txt) ? | ||||
| 
 | ||||
|  | @ -48,7 +47,7 @@ fn build(key string, repo_dir string) ? { | |||
| 		uuids << uuid | ||||
| 
 | ||||
| 		commands << "su builder -c 'git clone --single-branch --depth 1 --branch $repo.branch $repo.url /build/$uuid'" | ||||
| 		commands << 'su builder -c \'cd /build/$uuid && makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\${pkg}" -H "X-API-KEY: \$API_KEY" $server_url/publish; done\'' | ||||
| 		commands << 'su builder -c \'cd /build/$uuid && makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\${pkg}" -H "X-API-KEY: \$API_KEY" $conf.address/publish; done\'' | ||||
| 	} | ||||
| 
 | ||||
| 	// We convert the list of commands into a base64 string, which then gets | ||||
|  | @ -57,7 +56,7 @@ fn build(key string, repo_dir string) ? { | |||
| 
 | ||||
| 	c := docker.NewContainer{ | ||||
| 		image: 'archlinux:latest' | ||||
| 		env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$key'] | ||||
| 		env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$conf.api_key'] | ||||
| 		entrypoint: ['/bin/sh', '-c'] | ||||
| 		cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/sh -e'] | ||||
| 	} | ||||
|  |  | |||
|  | @ -0,0 +1,81 @@ | |||
| module env | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| // The prefix that every environment variable should have | ||||
| const prefix = 'VIETER_' | ||||
| 
 | ||||
| // The suffix an environment variable in order for it to be loaded from a file | ||||
| // instead | ||||
| const file_suffix = '_FILE' | ||||
| 
 | ||||
| pub struct ServerConfig { | ||||
| pub: | ||||
| 	log_level string [default: WARN] | ||||
| 	log_file string [default: 'vieter.log'] | ||||
| 	pkg_dir string | ||||
| 	download_dir string | ||||
| 	api_key string | ||||
| 	repo_dir string | ||||
| } | ||||
| 
 | ||||
| pub struct BuildConfig { | ||||
| pub: | ||||
| 	api_key string | ||||
| 	repo_dir string | ||||
| 	address string | ||||
| } | ||||
| 
 | ||||
| fn get_env_var(field_name string) ?string { | ||||
| 	env_var_name := '${prefix}${field_name.to_upper()}' | ||||
| 	env_file_name := '${prefix}${field_name.to_upper()}${file_suffix}' | ||||
| 	env_var := os.getenv(env_var_name) | ||||
| 	env_file := os.getenv(env_file_name) | ||||
| 
 | ||||
| 	// If both aren't set, we report them missing | ||||
| 	if env_var == '' && env_file == '' { | ||||
| 		return error('Either $env_var_name or $env_file_name is required.') | ||||
| 	} | ||||
| 
 | ||||
| 	// If they're both set, we report a conflict | ||||
| 	if env_var != '' && env_file != '' { | ||||
| 		return error('Only one of $env_var_name or $env_file_name can be defined.') | ||||
| 	} | ||||
| 
 | ||||
| 	// If it's the env var itself, we return it | ||||
| 	if env_var != '' { | ||||
| 		return env_var | ||||
| 	} | ||||
| 
 | ||||
| 	// Otherwise, we process the file | ||||
| 	return os.read_file(env_file) or { | ||||
| 		error('Failed to read file defined in $env_file_name: ${err.msg}.') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // load attempts to create the given type from environment variables. | ||||
| pub fn load<T>() ?T { | ||||
| 	res := T{} | ||||
| 
 | ||||
| 	$for field in T.fields { | ||||
| 		res.$(field.name) = get_env_var(field.name) or { | ||||
| 			// We use the default instead, if it's present | ||||
| 			mut default := '' | ||||
| 
 | ||||
| 			for attr in field.attrs { | ||||
| 				if attr.starts_with('default: ') { | ||||
| 					default = attr[9..] | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if default == '' { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			default | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return res | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/main.v
								
								
								
								
							
							
						
						
									
										24
									
								
								src/main.v
								
								
								
								
							|  | @ -1,22 +1,7 @@ | |||
| module main | ||||
| 
 | ||||
| import web | ||||
| import os | ||||
| import io | ||||
| import repo | ||||
| 
 | ||||
| const port = 8000 | ||||
| 
 | ||||
| const buf_size = 1_000_000 | ||||
| 
 | ||||
| struct App { | ||||
| 	web.Context | ||||
| pub: | ||||
| 	api_key string [required; web_global] | ||||
| 	dl_dir  string [required; web_global] | ||||
| pub mut: | ||||
| 	repo repo.Repo [required; web_global] | ||||
| } | ||||
| 
 | ||||
| [noreturn] | ||||
| fn exit_with_message(code int, msg string) { | ||||
|  | @ -51,18 +36,13 @@ fn reader_to_file(mut reader io.BufferedReader, length int, path string) ? { | |||
| } | ||||
| 
 | ||||
| fn main() { | ||||
| 	key := os.getenv_opt('API_KEY') or { exit_with_message(1, 'No API key was provided.') } | ||||
| 	repo_dir := os.getenv_opt('REPO_DIR') or { | ||||
| 		exit_with_message(1, 'No repo directory was configured.') | ||||
| 	} | ||||
| 
 | ||||
| 	if os.args.len == 1 { | ||||
| 		exit_with_message(1, 'No action provided.') | ||||
| 	} | ||||
| 
 | ||||
| 	match os.args[1] { | ||||
| 		'server' { server(key, repo_dir) } | ||||
| 		'build' { build(key, repo_dir) ? } | ||||
| 		'server' { server() ? } | ||||
| 		'build' { build() ? } | ||||
| 		else { exit_with_message(1, 'Unknown action: ${os.args[1]}') } | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -58,10 +58,10 @@ fn (mut app App) put_package() web.Result { | |||
| 
 | ||||
| 	if length := app.req.header.get(.content_length) { | ||||
| 		// Generate a random filename for the temp file | ||||
| 		pkg_path = os.join_path_single(app.dl_dir, rand.uuid_v4()) | ||||
| 		pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) | ||||
| 
 | ||||
| 		for os.exists(pkg_path) { | ||||
| 			pkg_path = os.join_path_single(app.dl_dir, rand.uuid_v4()) | ||||
| 			pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) | ||||
| 		} | ||||
| 
 | ||||
| 		app.ldebug("Uploading $length bytes (${pretty_bytes(length.int())}) to '$pkg_path'.") | ||||
|  |  | |||
							
								
								
									
										38
									
								
								src/server.v
								
								
								
								
							
							
						
						
									
										38
									
								
								src/server.v
								
								
								
								
							|  | @ -4,20 +4,33 @@ import web | |||
| import os | ||||
| import log | ||||
| import repo | ||||
| import env | ||||
| 
 | ||||
| const port = 8000 | ||||
| 
 | ||||
| const buf_size = 1_000_000 | ||||
| 
 | ||||
| struct App { | ||||
| 	web.Context | ||||
| pub: | ||||
| 	conf env.ServerConfig [required: web_global] | ||||
| pub mut: | ||||
| 	repo repo.Repo [required; web_global] | ||||
| } | ||||
| 
 | ||||
| fn server() ? { | ||||
| 	conf := env.load<env.ServerConfig>() ? | ||||
| 
 | ||||
| fn server(key string, repo_dir string) { | ||||
| 	// Configure logger | ||||
| 	log_level_str := os.getenv_opt('LOG_LEVEL') or { 'WARN' } | ||||
| 	log_level := log.level_from_tag(log_level_str) or { | ||||
| 	log_level := log.level_from_tag(conf.log_level) or { | ||||
| 		exit_with_message(1, 'Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.') | ||||
| 	} | ||||
| 	log_file := os.getenv_opt('LOG_FILE') or { 'vieter.log' } | ||||
| 
 | ||||
| 	mut logger := log.Log{ | ||||
| 		level: log_level | ||||
| 	} | ||||
| 
 | ||||
| 	logger.set_full_logpath(log_file) | ||||
| 	logger.set_full_logpath(conf.log_file) | ||||
| 	logger.log_to_console_too() | ||||
| 
 | ||||
| 	defer { | ||||
|  | @ -26,26 +39,17 @@ fn server(key string, repo_dir string) { | |||
| 		logger.close() | ||||
| 	} | ||||
| 
 | ||||
| 	// Configure web server | ||||
| 	pkg_dir := os.getenv_opt('PKG_DIR') or { | ||||
| 		exit_with_message(1, 'No package directory was configured.') | ||||
| 	} | ||||
| 	dl_dir := os.getenv_opt('DOWNLOAD_DIR') or { | ||||
| 		exit_with_message(1, 'No download directory was configured.') | ||||
| 	} | ||||
| 
 | ||||
| 	// This also creates the directories if needed | ||||
| 	repo := repo.new(repo_dir, pkg_dir) or { | ||||
| 	repo := repo.new(conf.repo_dir, conf.pkg_dir) or { | ||||
| 		logger.error(err.msg) | ||||
| 		exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	os.mkdir_all(dl_dir) or { exit_with_message(1, 'Failed to create download directory.') } | ||||
| 	os.mkdir_all(conf.download_dir) or { exit_with_message(1, 'Failed to create download directory.') } | ||||
| 
 | ||||
| 	web.run(&App{ | ||||
| 		logger: logger | ||||
| 		api_key: key | ||||
| 		dl_dir: dl_dir | ||||
| 		conf: conf | ||||
| 		repo: repo | ||||
| 	}, port) | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue