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