forked from vieter-v/vieter
				
			Merge pull request 'improve CLI experience; merge binaries' (#115) from merge-cli-server into dev
Reviewed-on: Chewing_Bever/vieter#115cron
						commit
						94f96feb5d
					
				|  | @ -2,7 +2,6 @@ matrix: | ||||||
|   PLATFORM: |   PLATFORM: | ||||||
|     - linux/amd64 |     - linux/amd64 | ||||||
|     - linux/arm64 |     - linux/arm64 | ||||||
|     - linux/arm/v7 |  | ||||||
| 
 | 
 | ||||||
| # These checks already get performed on the feature branches | # These checks already get performed on the feature branches | ||||||
| platform: ${PLATFORM} | platform: ${PLATFORM} | ||||||
|  | @ -36,22 +35,6 @@ pipeline: | ||||||
|     when: |     when: | ||||||
|       event: push |       event: push | ||||||
| 
 | 
 | ||||||
|   cli: |  | ||||||
|     image: 'chewingbever/vlang:latest' |  | ||||||
|     environment: |  | ||||||
|       - LDFLAGS=-static |  | ||||||
|     commands: |  | ||||||
|       - make cli-prod |  | ||||||
|       # Make sure the binary is actually statically built |  | ||||||
|       - readelf -d vieterctl |  | ||||||
|       - du -h vieterctl |  | ||||||
|       - '[ "$(readelf -d vieterctl | grep NEEDED | wc -l)" = 0 ]' |  | ||||||
|       # This removes so much, it's amazing |  | ||||||
|       - strip -s vieterctl |  | ||||||
|       - du -h vieterctl |  | ||||||
|     when: |  | ||||||
|       event: push |  | ||||||
| 
 |  | ||||||
|   upload: |   upload: | ||||||
|     image: 'chewingbever/vlang:latest' |     image: 'chewingbever/vlang:latest' | ||||||
|     secrets: [ s3_username, s3_password ] |     secrets: [ s3_username, s3_password ] | ||||||
|  | @ -74,20 +57,5 @@ pipeline: | ||||||
|         -H "Content-Type: $CONTENT_TYPE" |         -H "Content-Type: $CONTENT_TYPE" | ||||||
|         -H "Authorization: AWS $S3_USERNAME:$SIGNATURE" |         -H "Authorization: AWS $S3_USERNAME:$SIGNATURE" | ||||||
|         https://$URL$OBJ_PATH |         https://$URL$OBJ_PATH | ||||||
| 
 |  | ||||||
|       # Also update the CLI tool |  | ||||||
|       - export OBJ_PATH="/vieter/commits/$CI_COMMIT_SHA/vieterctl-$(echo '${PLATFORM}' | sed 's:/:-:g')" |  | ||||||
|       - export SIG_STRING="PUT\n\n$CONTENT_TYPE\n$DATE\n$OBJ_PATH" |  | ||||||
|       - export SIGNATURE=`echo -en $SIG_STRING | openssl sha1 -hmac $S3_PASSWORD -binary | base64` |  | ||||||
|       - > |  | ||||||
|         curl  |  | ||||||
|         --silent |  | ||||||
|         -XPUT |  | ||||||
|         -T vieterctl |  | ||||||
|         -H "Host: $URL" |  | ||||||
|         -H "Date: $DATE" |  | ||||||
|         -H "Content-Type: $CONTENT_TYPE" |  | ||||||
|         -H "Authorization: AWS $S3_USERNAME:$SIGNATURE" |  | ||||||
|         https://$URL$OBJ_PATH |  | ||||||
|     when: |     when: | ||||||
|       event: push |       event: push | ||||||
|  |  | ||||||
							
								
								
									
										35
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										35
									
								
								Makefile
								
								
								
								
							|  | @ -23,13 +23,7 @@ dvieter: $(SOURCES) | ||||||
| # Run the debug build inside gdb
 | # Run the debug build inside gdb
 | ||||||
| .PHONY: gdb | .PHONY: gdb | ||||||
| gdb: dvieter | gdb: dvieter | ||||||
| 	 VIETER_API_KEY=test \
 | 		gdb --args './dvieter -f vieter.toml server' | ||||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 |  | ||||||
| 		VIETER_REPO_DIR=data/repo \
 |  | ||||||
| 		VIETER_PKG_DIR=data/pkgs \
 |  | ||||||
| 		VIETER_LOG_LEVEL=DEBUG \
 |  | ||||||
| 		VIETER_REPOS_FILE=data/repos.json \
 |  | ||||||
| 		gdb --args ./dvieter |  | ||||||
| 
 | 
 | ||||||
| # Optimised production build
 | # Optimised production build
 | ||||||
| .PHONY: prod | .PHONY: prod | ||||||
|  | @ -42,38 +36,15 @@ pvieter: $(SOURCES) | ||||||
| c: | c: | ||||||
| 	$(V) -o vieter.c $(SRC_DIR) | 	$(V) -o vieter.c $(SRC_DIR) | ||||||
| 
 | 
 | ||||||
| # Build the CLI tool
 |  | ||||||
| .PHONY: cli |  | ||||||
| cli: dvieterctl |  | ||||||
| dvieterctl: cli.v |  | ||||||
| 	$(V_PATH) -showcc -g -o dvieterctl cli.v |  | ||||||
| 
 |  | ||||||
| .PHONY: cli-prod |  | ||||||
| cli-prod: vieterctl |  | ||||||
| vieterctl: cli.v |  | ||||||
| cli-prod: |  | ||||||
| 	$(V_PATH) -showcc -o vieterctl -prod cli.v |  | ||||||
| 
 |  | ||||||
| # =====EXECUTION=====
 | # =====EXECUTION=====
 | ||||||
| # Run the server in the default 'data' directory
 | # Run the server in the default 'data' directory
 | ||||||
| .PHONY: run | .PHONY: run | ||||||
| run: vieter | run: vieter | ||||||
| 	 VIETER_API_KEY=test \
 | 		./vieter -f vieter.toml server | ||||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 |  | ||||||
| 		VIETER_REPO_DIR=data/repo \
 |  | ||||||
| 		VIETER_PKG_DIR=data/pkgs \
 |  | ||||||
| 		VIETER_LOG_LEVEL=DEBUG \
 |  | ||||||
| 		VIETER_REPOS_FILE=data/repos.json \
 |  | ||||||
| 		./vieter server |  | ||||||
| 
 | 
 | ||||||
| .PHONY: run-prod | .PHONY: run-prod | ||||||
| run-prod: prod | run-prod: prod | ||||||
| 	VIETER_API_KEY=test \
 | 	./pvieter -f vieter.toml server | ||||||
| 		VIETER_DOWNLOAD_DIR=data/downloads \
 |  | ||||||
| 		VIETER_REPO_DIR=data/repo \
 |  | ||||||
| 		VIETER_PKG_DIR=data/pkgs \
 |  | ||||||
| 		VIETER_LOG_LEVEL=DEBUG \
 |  | ||||||
| 	./pvieter server |  | ||||||
| 
 | 
 | ||||||
| # =====OTHER=====
 | # =====OTHER=====
 | ||||||
| .PHONY: lint | .PHONY: lint | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								PKGBUILD
								
								
								
								
							
							
						
						
									
										13
									
								
								PKGBUILD
								
								
								
								
							|  | @ -1,7 +1,7 @@ | ||||||
| # Maintainer: Jef Roosens | # Maintainer: Jef Roosens | ||||||
| 
 | 
 | ||||||
| pkgbase='vieter' | pkgbase='vieter' | ||||||
| pkgname=('vieter' 'vieterctl') | pkgname='vieter' | ||||||
| pkgver=0.1.0.rc1.r45.g6d3ff8a | pkgver=0.1.0.rc1.r45.g6d3ff8a | ||||||
| pkgrel=1 | pkgrel=1 | ||||||
| depends=('glibc' 'openssl' 'libarchive' 'gc') | depends=('glibc' 'openssl' 'libarchive' 'gc') | ||||||
|  | @ -23,21 +23,12 @@ build() { | ||||||
|     # Build the compiler |     # Build the compiler | ||||||
|     CFLAGS= make v |     CFLAGS= make v | ||||||
| 
 | 
 | ||||||
|     # Build the server & the CLI tool |  | ||||||
|     make prod |     make prod | ||||||
|     make cli-prod |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| package_vieter() { | package() { | ||||||
|     pkgdesc="Vieter is a lightweight implementation of an Arch repository server." |     pkgdesc="Vieter is a lightweight implementation of an Arch repository server." | ||||||
|     install -dm755 "$pkgdir/usr/bin" |     install -dm755 "$pkgdir/usr/bin" | ||||||
| 
 | 
 | ||||||
|     install -Dm755 "$pkgbase/pvieter" "$pkgdir/usr/bin/vieter" |     install -Dm755 "$pkgbase/pvieter" "$pkgdir/usr/bin/vieter" | ||||||
| } | } | ||||||
| 
 |  | ||||||
| package_vieterctl() { |  | ||||||
|     pkgdesc="Allows you to configure a Vieter server's list of Git repositories." |  | ||||||
|     install -dm755 "$pkgdir/usr/bin" |  | ||||||
| 
 |  | ||||||
|     install -Dm755 "$pkgbase/vieterctl" "$pkgdir/usr/bin/vieterctl" |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										84
									
								
								cli.v
								
								
								
								
							
							
						
						
									
										84
									
								
								cli.v
								
								
								
								
							|  | @ -1,84 +0,0 @@ | ||||||
| import os |  | ||||||
| import toml |  | ||||||
| import net.http |  | ||||||
| 
 |  | ||||||
| struct Config { |  | ||||||
| 	address string [required] |  | ||||||
| 	api_key string [required] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn list(conf Config) ? { |  | ||||||
| 	mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? |  | ||||||
| 	req.add_custom_header('X-API-Key', conf.api_key) ? |  | ||||||
| 
 |  | ||||||
| 	res := req.do() ? |  | ||||||
| 
 |  | ||||||
| 	println(res.text) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn add(conf Config, args []string) ? { |  | ||||||
| 	if args.len < 2 { |  | ||||||
| 		eprintln('Not enough arguments.') |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if args.len > 2 { |  | ||||||
| 		eprintln('Too many arguments.') |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mut req := http.new_request(http.Method.post, '$conf.address/api/repos?url=${args[0]}&branch=${args[1]}', '') ? |  | ||||||
| 	req.add_custom_header('X-API-Key', conf.api_key) ? |  | ||||||
| 
 |  | ||||||
| 	res := req.do() ? |  | ||||||
| 
 |  | ||||||
| 	println(res.text) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn remove(conf Config, args []string) ? { |  | ||||||
| 	if args.len < 2 { |  | ||||||
| 		eprintln('Not enough arguments.') |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if args.len > 2 { |  | ||||||
| 		eprintln('Too many arguments.') |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	mut req := http.new_request(http.Method.delete, '$conf.address/api/repos?url=${args[0]}&branch=${args[1]}', '') ? |  | ||||||
| 	req.add_custom_header('X-API-Key', conf.api_key) ? |  | ||||||
| 
 |  | ||||||
| 	res := req.do() ? |  | ||||||
| 
 |  | ||||||
| 	println(res.text) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn main() { |  | ||||||
| 	conf_path := os.expand_tilde_to_home('~/.vieterrc') |  | ||||||
| 
 |  | ||||||
| 	if !os.is_file(conf_path) { |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	conf := toml.parse_file(conf_path) ?.reflect<Config>() |  | ||||||
| 
 |  | ||||||
| 	args := os.args[1..] |  | ||||||
| 
 |  | ||||||
| 	if args.len == 0 { |  | ||||||
| 		eprintln('No action provided.') |  | ||||||
| 		exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	action := args[0] |  | ||||||
| 
 |  | ||||||
| 	match action { |  | ||||||
| 		'list' { list(conf) ? } |  | ||||||
| 		'add' { add(conf, args[1..]) ? } |  | ||||||
| 		'remove' { remove(conf, args[1..]) ? } |  | ||||||
| 		else { |  | ||||||
| 			eprintln("Invalid action '$action'.") |  | ||||||
| 			exit(1) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,12 +1,11 @@ | ||||||
| module main | module build | ||||||
| 
 | 
 | ||||||
| import docker | import docker | ||||||
| import encoding.base64 | import encoding.base64 | ||||||
| import time | import time | ||||||
| import json |  | ||||||
| import server |  | ||||||
| import env |  | ||||||
| import net.http | import net.http | ||||||
|  | import git | ||||||
|  | import json | ||||||
| 
 | 
 | ||||||
| const container_build_dir = '/build' | const container_build_dir = '/build' | ||||||
| 
 | 
 | ||||||
|  | @ -62,15 +61,13 @@ fn create_build_image() ?string { | ||||||
| 	return image.id | 	return image.id | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn build() ? { | fn build(conf Config) ? { | ||||||
| 	conf := env.load<env.BuildConfig>() ? |  | ||||||
| 
 |  | ||||||
| 	// We get the repos list from the Vieter instance | 	// We get the repos list from the Vieter instance | ||||||
| 	mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? | 	mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? | ||||||
| 	req.add_custom_header('X-Api-Key', conf.api_key) ? | 	req.add_custom_header('X-Api-Key', conf.api_key) ? | ||||||
| 
 | 
 | ||||||
| 	res := req.do() ? | 	res := req.do() ? | ||||||
| 	repos := json.decode([]server.GitRepo, res.text) ? | 	repos := json.decode([]git.GitRepo, res.text) ? | ||||||
| 
 | 
 | ||||||
| 	// No point in doing work if there's no repos present | 	// No point in doing work if there's no repos present | ||||||
| 	if repos.len == 0 { | 	if repos.len == 0 { | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | module build | ||||||
|  | 
 | ||||||
|  | import cli | ||||||
|  | import env | ||||||
|  | 
 | ||||||
|  | pub struct Config { | ||||||
|  | pub: | ||||||
|  | 	api_key string | ||||||
|  | 	address string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // cmd returns the cli submodule that handles the build process | ||||||
|  | pub fn cmd() cli.Command { | ||||||
|  | 	return cli.Command{ | ||||||
|  | 		name: 'build' | ||||||
|  | 		description: 'Run the build process.' | ||||||
|  | 		execute: fn (cmd cli.Command) ? { | ||||||
|  | 			config_file := cmd.flags.get_string('config-file') ? | ||||||
|  | 			conf := env.load<Config>(config_file) ? | ||||||
|  | 
 | ||||||
|  | 			build(conf) ? | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								src/env.v
								
								
								
								
							
							
						
						
									
										68
									
								
								src/env.v
								
								
								
								
							|  | @ -1,6 +1,7 @@ | ||||||
| module env | module env | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
|  | import toml | ||||||
| 
 | 
 | ||||||
| // The prefix that every environment variable should have | // The prefix that every environment variable should have | ||||||
| const prefix = 'VIETER_' | const prefix = 'VIETER_' | ||||||
|  | @ -9,32 +10,15 @@ const prefix = 'VIETER_' | ||||||
| // instead | // instead | ||||||
| const file_suffix = '_FILE' | 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 |  | ||||||
| 	repos_file   string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub struct BuildConfig { |  | ||||||
| pub: |  | ||||||
| 	api_key string |  | ||||||
| 	address string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn get_env_var(field_name string) ?string { | fn get_env_var(field_name string) ?string { | ||||||
| 	env_var_name := '$env.prefix$field_name.to_upper()' | 	env_var_name := '$env.prefix$field_name.to_upper()' | ||||||
| 	env_file_name := '$env.prefix$field_name.to_upper()$env.file_suffix' | 	env_file_name := '$env.prefix$field_name.to_upper()$env.file_suffix' | ||||||
| 	env_var := os.getenv(env_var_name) | 	env_var := os.getenv(env_var_name) | ||||||
| 	env_file := os.getenv(env_file_name) | 	env_file := os.getenv(env_file_name) | ||||||
| 
 | 
 | ||||||
| 	// If both aren't set, we report them missing | 	// If both are missing, we return an empty string | ||||||
| 	if env_var == '' && env_file == '' { | 	if env_var == '' && env_file == '' { | ||||||
| 		return error('Either $env_var_name or $env_file_name is required.') | 		return '' | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// If they're both set, we report a conflict | 	// If they're both set, we report a conflict | ||||||
|  | @ -56,30 +40,42 @@ fn get_env_var(field_name string) ?string { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // load<T> attempts to create the given type from environment variables. For | // load<T> attempts to create an object of type T from the given path to a toml | ||||||
| // each field, the corresponding env var is its name in uppercase prepended | // file & environment variables. For each field, it will select either a value | ||||||
| // with the hardcoded prefix. If this one isn't present, it looks for the env | // given from an environment variable, a value defined in the config file or a | ||||||
| // var with the file_suffix suffix. | // configured default if present, in that order. | ||||||
| pub fn load<T>() ?T { | pub fn load<T>(path string) ?T { | ||||||
| 	res := T{} | 	mut res := T{} | ||||||
|  | 
 | ||||||
|  | 	if os.exists(path) { | ||||||
|  | 		// We don't use reflect here because reflect also sets any fields not | ||||||
|  | 		// in the toml back to their zero value, which we don't want | ||||||
|  | 		doc := toml.parse_file(path) ? | ||||||
| 
 | 
 | ||||||
| 		$for field in T.fields { | 		$for field in T.fields { | ||||||
| 		res.$(field.name) = get_env_var(field.name) or { | 			s := doc.value(field.name) | ||||||
| 			// We use the default instead, if it's present |  | ||||||
| 			mut default := '' |  | ||||||
| 
 | 
 | ||||||
| 			for attr in field.attrs { | 			// We currently only support strings | ||||||
| 				if attr.starts_with('default: ') { | 			if s.type_name() == 'string' { | ||||||
| 					default = attr[9..] | 				res.$(field.name) = s.string() | ||||||
| 					break | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 			if default == '' { | 	$for field in T.fields { | ||||||
| 				return err | 		$if field.typ is string { | ||||||
| 			} | 			env_value := get_env_var(field.name) ? | ||||||
| 
 | 
 | ||||||
| 			default | 			// The value of the env var will always be chosen over the config | ||||||
|  | 			// file | ||||||
|  | 			if env_value != '' { | ||||||
|  | 				res.$(field.name) = env_value | ||||||
|  | 			} | ||||||
|  | 			// If there's no value from the toml file either, we try to find a | ||||||
|  | 			// default value | ||||||
|  | 			else if res.$(field.name) == '' { | ||||||
|  | 				return error("Missing config variable '$field.name' with no provided default. Either add it to the config file or provide it using an environment variable.") | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return res | 	return res | ||||||
|  |  | ||||||
|  | @ -0,0 +1,83 @@ | ||||||
|  | module git | ||||||
|  | 
 | ||||||
|  | import cli | ||||||
|  | import env | ||||||
|  | import net.http | ||||||
|  | 
 | ||||||
|  | struct Config { | ||||||
|  | 	address string [required] | ||||||
|  | 	api_key string [required] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // cmd returns the cli submodule that handles the repos API interaction | ||||||
|  | pub fn cmd() cli.Command { | ||||||
|  | 	return cli.Command{ | ||||||
|  | 		name: 'repos' | ||||||
|  | 		description: 'Interact with the repos API.' | ||||||
|  | 		commands: [ | ||||||
|  | 			cli.Command{ | ||||||
|  | 				name: 'list' | ||||||
|  | 				description: 'List the current repos.' | ||||||
|  | 				execute: fn (cmd cli.Command) ? { | ||||||
|  | 					config_file := cmd.flags.get_string('config-file') ? | ||||||
|  | 					conf := env.load<Config>(config_file) ? | ||||||
|  | 
 | ||||||
|  | 					list(conf) ? | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			cli.Command{ | ||||||
|  | 				name: 'add' | ||||||
|  | 				required_args: 2 | ||||||
|  | 				usage: 'url branch' | ||||||
|  | 				description: 'Add a new repository.' | ||||||
|  | 				execute: fn (cmd cli.Command) ? { | ||||||
|  | 					config_file := cmd.flags.get_string('config-file') ? | ||||||
|  | 					conf := env.load<Config>(config_file) ? | ||||||
|  | 
 | ||||||
|  | 					add(conf, cmd.args[0], cmd.args[1]) ? | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			cli.Command{ | ||||||
|  | 				name: 'remove' | ||||||
|  | 				required_args: 2 | ||||||
|  | 				usage: 'url branch' | ||||||
|  | 				description: 'Remove a repository.' | ||||||
|  | 				execute: fn (cmd cli.Command) ? { | ||||||
|  | 					config_file := cmd.flags.get_string('config-file') ? | ||||||
|  | 					conf := env.load<Config>(config_file) ? | ||||||
|  | 
 | ||||||
|  | 					remove(conf, cmd.args[0], cmd.args[1]) ? | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 		] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn list(conf Config) ? { | ||||||
|  | 	mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? | ||||||
|  | 	req.add_custom_header('X-API-Key', conf.api_key) ? | ||||||
|  | 
 | ||||||
|  | 	res := req.do() ? | ||||||
|  | 
 | ||||||
|  | 	println(res.text) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn add(conf Config, url string, branch string) ? { | ||||||
|  | 	mut req := http.new_request(http.Method.post, '$conf.address/api/repos?url=$url&branch=$branch', | ||||||
|  | 		'') ? | ||||||
|  | 	req.add_custom_header('X-API-Key', conf.api_key) ? | ||||||
|  | 
 | ||||||
|  | 	res := req.do() ? | ||||||
|  | 
 | ||||||
|  | 	println(res.text) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn remove(conf Config, url string, branch string) ? { | ||||||
|  | 	mut req := http.new_request(http.Method.delete, '$conf.address/api/repos?url=$url&branch=$branch', | ||||||
|  | 		'') ? | ||||||
|  | 	req.add_custom_header('X-API-Key', conf.api_key) ? | ||||||
|  | 
 | ||||||
|  | 	res := req.do() ? | ||||||
|  | 
 | ||||||
|  | 	println(res.text) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | module git | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | pub struct GitRepo { | ||||||
|  | pub: | ||||||
|  | 	url    string [required] | ||||||
|  | 	branch string [required] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // read_repos reads the given JSON file & parses it as a list of Git repos | ||||||
|  | pub fn read_repos(path string) ?[]GitRepo { | ||||||
|  | 	if !os.exists(path) { | ||||||
|  | 		mut f := os.create(path) ? | ||||||
|  | 
 | ||||||
|  | 		defer { | ||||||
|  | 			f.close() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		f.write_string('[]') ? | ||||||
|  | 
 | ||||||
|  | 		return [] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	content := os.read_file(path) ? | ||||||
|  | 	res := json.decode([]GitRepo, content) ? | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // write_repos writes a list of repositories back to a given file | ||||||
|  | pub fn write_repos(path string, repos []GitRepo) ? { | ||||||
|  | 	mut f := os.create(path) ? | ||||||
|  | 
 | ||||||
|  | 	defer { | ||||||
|  | 		f.close() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	value := json.encode(repos) | ||||||
|  | 	f.write_string(value) ? | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/main.v
								
								
								
								
							
							
						
						
									
										32
									
								
								src/main.v
								
								
								
								
							|  | @ -2,16 +2,32 @@ module main | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
| import server | import server | ||||||
| import util | import cli | ||||||
|  | import build | ||||||
|  | import git | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
| 	if os.args.len == 1 { | 	mut app := cli.Command{ | ||||||
| 		util.exit_with_message(1, 'No action provided.') | 		name: 'vieter' | ||||||
|  | 		description: 'Vieter is a lightweight implementation of an Arch repository server.' | ||||||
|  | 		version: '0.1.0' | ||||||
|  | 		flags: [ | ||||||
|  | 			cli.Flag{ | ||||||
|  | 				flag: cli.FlagType.string | ||||||
|  | 				name: 'config-file' | ||||||
|  | 				abbrev: 'f' | ||||||
|  | 				description: 'Location of Vieter config file; defaults to ~/.vieterrc.' | ||||||
|  | 				global: true | ||||||
|  | 				default_value: [os.expand_tilde_to_home('~/.vieterrc')] | ||||||
|  | 			}, | ||||||
|  | 		] | ||||||
|  | 		commands: [ | ||||||
|  | 			server.cmd(), | ||||||
|  | 			build.cmd(), | ||||||
|  | 			git.cmd(), | ||||||
|  | 		] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	match os.args[1] { | 	app.setup() | ||||||
| 		'server' { server.server() ? } | 	app.parse(os.args) | ||||||
| 		'build' { build() ? } |  | ||||||
| 		else { util.exit_with_message(1, 'Unknown action: ${os.args[1]}') } |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | module server | ||||||
|  | 
 | ||||||
|  | import cli | ||||||
|  | import env | ||||||
|  | 
 | ||||||
|  | struct Config { | ||||||
|  | pub: | ||||||
|  | 	log_level    string = 'WARN' | ||||||
|  | 	log_file     string = 'vieter.log' | ||||||
|  | 	pkg_dir      string | ||||||
|  | 	download_dir string | ||||||
|  | 	api_key      string | ||||||
|  | 	repo_dir     string | ||||||
|  | 	repos_file   string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // cmd returns the cli submodule that handles starting the server | ||||||
|  | pub fn cmd() cli.Command { | ||||||
|  | 	return cli.Command{ | ||||||
|  | 		name: 'server' | ||||||
|  | 		description: 'Start the Vieter server.' | ||||||
|  | 		execute: fn (cmd cli.Command) ? { | ||||||
|  | 			config_file := cmd.flags.get_string('config-file') ? | ||||||
|  | 			conf := env.load<Config>(config_file) ? | ||||||
|  | 
 | ||||||
|  | 			server(conf) ? | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,46 +1,10 @@ | ||||||
| module server | module server | ||||||
| 
 | 
 | ||||||
| import web | import web | ||||||
| import os | import git | ||||||
| import json |  | ||||||
| 
 | 
 | ||||||
| const repos_file = 'repos.json' | const repos_file = 'repos.json' | ||||||
| 
 | 
 | ||||||
| pub struct GitRepo { |  | ||||||
| pub: |  | ||||||
| 	url    string [required] |  | ||||||
| 	branch string [required] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn read_repos(path string) ?[]GitRepo { |  | ||||||
| 	if !os.exists(path) { |  | ||||||
| 		mut f := os.create(path) ? |  | ||||||
| 
 |  | ||||||
| 		defer { |  | ||||||
| 			f.close() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		f.write_string('[]') ? |  | ||||||
| 
 |  | ||||||
| 		return [] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	content := os.read_file(path) ? |  | ||||||
| 	res := json.decode([]GitRepo, content) ? |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn write_repos(path string, repos []GitRepo) ? { |  | ||||||
| 	mut f := os.create(path) ? |  | ||||||
| 
 |  | ||||||
| 	defer { |  | ||||||
| 		f.close() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	value := json.encode(repos) |  | ||||||
| 	f.write_string(value) ? |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ['/api/repos'; get] | ['/api/repos'; get] | ||||||
| fn (mut app App) get_repos() web.Result { | fn (mut app App) get_repos() web.Result { | ||||||
| 	if !app.is_authorized() { | 	if !app.is_authorized() { | ||||||
|  | @ -48,7 +12,7 @@ fn (mut app App) get_repos() web.Result { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	repos := rlock app.git_mutex { | 	repos := rlock app.git_mutex { | ||||||
| 		read_repos(app.conf.repos_file) or { | 		git.read_repos(app.conf.repos_file) or { | ||||||
| 			app.lerror('Failed to read repos file.') | 			app.lerror('Failed to read repos file.') | ||||||
| 
 | 
 | ||||||
| 			return app.server_error(500) | 			return app.server_error(500) | ||||||
|  | @ -68,13 +32,13 @@ fn (mut app App) post_repo() web.Result { | ||||||
| 		return app.server_error(400) | 		return app.server_error(400) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	new_repo := GitRepo{ | 	new_repo := git.GitRepo{ | ||||||
| 		url: app.query['url'] | 		url: app.query['url'] | ||||||
| 		branch: app.query['branch'] | 		branch: app.query['branch'] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mut repos := rlock app.git_mutex { | 	mut repos := rlock app.git_mutex { | ||||||
| 		read_repos(app.conf.repos_file) or { | 		git.read_repos(app.conf.repos_file) or { | ||||||
| 			app.lerror('Failed to read repos file.') | 			app.lerror('Failed to read repos file.') | ||||||
| 
 | 
 | ||||||
| 			return app.server_error(500) | 			return app.server_error(500) | ||||||
|  | @ -91,7 +55,7 @@ fn (mut app App) post_repo() web.Result { | ||||||
| 	repos << new_repo | 	repos << new_repo | ||||||
| 
 | 
 | ||||||
| 	lock app.git_mutex { | 	lock app.git_mutex { | ||||||
| 		write_repos(app.conf.repos_file, repos) or { return app.server_error(500) } | 		git.write_repos(app.conf.repos_file, repos) or { return app.server_error(500) } | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return app.ok('Repo added successfully.') | 	return app.ok('Repo added successfully.') | ||||||
|  | @ -107,13 +71,13 @@ fn (mut app App) delete_repo() web.Result { | ||||||
| 		return app.server_error(400) | 		return app.server_error(400) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	repo_to_remove := GitRepo{ | 	repo_to_remove := git.GitRepo{ | ||||||
| 		url: app.query['url'] | 		url: app.query['url'] | ||||||
| 		branch: app.query['branch'] | 		branch: app.query['branch'] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mut repos := rlock app.git_mutex { | 	mut repos := rlock app.git_mutex { | ||||||
| 		read_repos(app.conf.repos_file) or { | 		git.read_repos(app.conf.repos_file) or { | ||||||
| 			app.lerror('Failed to read repos file.') | 			app.lerror('Failed to read repos file.') | ||||||
| 
 | 
 | ||||||
| 			return app.server_error(500) | 			return app.server_error(500) | ||||||
|  | @ -122,7 +86,7 @@ fn (mut app App) delete_repo() web.Result { | ||||||
| 	filtered := repos.filter(it != repo_to_remove) | 	filtered := repos.filter(it != repo_to_remove) | ||||||
| 
 | 
 | ||||||
| 	lock app.git_mutex { | 	lock app.git_mutex { | ||||||
| 		write_repos(app.conf.repos_file, filtered) or { return app.server_error(500) } | 		git.write_repos(app.conf.repos_file, filtered) or { return app.server_error(500) } | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return app.ok('Repo removed successfully.') | 	return app.ok('Repo removed successfully.') | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ import web | ||||||
| import os | import os | ||||||
| import log | import log | ||||||
| import repo | import repo | ||||||
| import env |  | ||||||
| import util | import util | ||||||
| 
 | 
 | ||||||
| const port = 8000 | const port = 8000 | ||||||
|  | @ -12,7 +11,7 @@ const port = 8000 | ||||||
| struct App { | struct App { | ||||||
| 	web.Context | 	web.Context | ||||||
| pub: | pub: | ||||||
| 	conf env.ServerConfig [required; web_global] | 	conf Config [required; web_global] | ||||||
| pub mut: | pub mut: | ||||||
| 	repo repo.Repo [required; web_global] | 	repo repo.Repo [required; web_global] | ||||||
| 	// This is used to claim the file lock on the repos file | 	// This is used to claim the file lock on the repos file | ||||||
|  | @ -20,9 +19,7 @@ pub mut: | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // server starts the web server & starts listening for requests | // server starts the web server & starts listening for requests | ||||||
| pub fn server() ? { | pub fn server(conf Config) ? { | ||||||
| 	conf := env.load<env.ServerConfig>() ? |  | ||||||
| 
 |  | ||||||
| 	// Configure logger | 	// Configure logger | ||||||
| 	log_level := log.level_from_tag(conf.log_level) or { | 	log_level := log.level_from_tag(conf.log_level) or { | ||||||
| 		util.exit_with_message(1, 'Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.') | 		util.exit_with_message(1, 'Invalid log level. The allowed values are FATAL, ERROR, WARN, INFO & DEBUG.') | ||||||
|  |  | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # This file contains settings used during development | ||||||
|  | api_key = "test" | ||||||
|  | download_dir = "data/downloads" | ||||||
|  | repo_dir = "data/repo" | ||||||
|  | pkg_dir = "data/pkgs" | ||||||
|  | # log_level = "DEBUG" | ||||||
|  | repos_file = "data/repos.json" | ||||||
|  | 
 | ||||||
|  | address = "http://localhost:8000" | ||||||
		Loading…
	
		Reference in New Issue