123456789module cli1011import json12import os13import src.machine14import src.remote1516pub fn cmd_remote(raw []string) int {17 args := parse('remote', raw, Options{18 flags: ['json', 'forget']19 }) or { return usage_error(err.msg()) }20 if args.flag('forget') {21 if !machine.has_remote() {22 return refuse(args.json(), 'this machine has no remote to forget')23 }24 machine.forget_remote() or { return refuse(args.json(), err.msg()) }25 if args.json() {26 emit_json(json.encode(RemoteJson{27 configured: false28 }))29 return 030 }31 ok("forgot this machine's remote")32 return 033 }34 if args.positional.len == 0 {35 if !machine.has_remote() {36 if args.json() {37 emit_json(json.encode(RemoteJson{38 configured: false39 }))40 return 041 }42 return setup_wizard()43 }44 settings := machine.load_remote() or { return refuse(args.json(), err.msg()) }45 if args.json() {46 emit_json(json.encode(RemoteJson{47 configured: true48 kind: settings.kind49 target: describe(settings)50 }))51 return 052 }53 ok('remote: ${describe(settings)}')54 note('run "koh remote folder <path>" or "koh remote s3 <endpoint> <bucket> <region>" to replace it')55 return 056 }57 58 return set_remote(args)59}6061fn set_remote(args Args) int {62 kind := args.positional_at(0)63 match kind {64 machine.kind_folder {65 path := args.positional_at(1)66 if path == '' {67 return usage_error('remote folder needs a path: koh remote folder /Volumes/usb/koh')68 }69 settings := machine.RemoteSettings{70 kind: machine.kind_folder71 path: os.real_path(path)72 }73 return save_after_check(settings, args, '', '')74 }75 machine.kind_s3 {76 endpoint := args.positional_at(1)77 bucket := args.positional_at(2)78 region := args.positional_at(3)79 if endpoint == '' || bucket == '' || region == '' {80 return usage_error('remote s3 needs an endpoint, bucket and region: koh remote s3 https://s3.fr-par.scw.cloud my-bucket fr-par')81 }82 access_key, secret_key := s3_keys() or { return refuse(args.json(), err.msg()) }83 settings := machine.RemoteSettings{84 kind: machine.kind_s385 endpoint: endpoint86 bucket: bucket87 region: region88 }89 return save_after_check(settings, args, access_key, secret_key)90 }91 else {92 return usage_error('remote takes "folder" or "s3", not "${kind}"')93 }94 }95}96979899fn save_after_check(settings machine.RemoteSettings, args Args, access_key string, secret_key string) int {100 mut backend := build_backend(settings, access_key, secret_key) or {101 return refuse(args.json(), err.msg())102 }103 remote.check(mut backend) or {104 return refuse(args.json(), 'the remote did not pass its test: ${err.msg()}')105 }106 machine.save_remote(settings) or { return refuse(args.json(), err.msg()) }107 if settings.kind == machine.kind_s3 {108 machine.save_credentials(machine.Credentials{109 access_key: access_key110 secret_key: secret_key111 }) or { return refuse(args.json(), err.msg()) }112 }113 if args.json() {114 emit_json(json.encode(RemoteJson{115 configured: true116 kind: settings.kind117 target: describe(settings)118 }))119 return 0120 }121 ok('remote set to ${describe(settings)}')122 note('every repo on this machine now offers to it')123 return 0124}125126fn build_backend(settings machine.RemoteSettings, access_key string, secret_key string) !remote.Backend {127 if settings.kind == machine.kind_folder {128 return remote.new_folder(settings.path)!129 }130 return remote.new_s3(settings.endpoint, settings.bucket, settings.region, access_key,131 secret_key)!132}133134fn setup_wizard() int {135 if !interactive() {136 return fail('setting up a remote needs a terminal, or use: koh remote folder <path>, koh remote s3 <endpoint> <bucket> <region>')137 }138 println(' choose a remote:')139 println(' ${amber('1')} folder a directory, on a disk or a NAS')140 println(' ${amber('2')} s3 an S3-compatible bucket')141 print(' which? [1] ')142 if os.get_line().trim_space() == '2' {143 return wizard_s3()144 }145 print(' folder path: ')146 path := os.get_line().trim_space()147 if path == '' {148 return fail('no path given — nothing was saved')149 }150 settings := machine.RemoteSettings{151 kind: machine.kind_folder152 path: os.real_path(path)153 }154 return save_after_check(settings, Args{}, '', '')155}156157fn wizard_s3() int {158 print(' endpoint [https://s3.fr-par.scw.cloud]: ')159 endpoint := or_default(os.get_line().trim_space(), 'https://s3.fr-par.scw.cloud')160 print(' bucket: ')161 bucket := os.get_line().trim_space()162 print(' region [fr-par]: ')163 region := or_default(os.get_line().trim_space(), 'fr-par')164 if bucket == '' {165 return fail('no bucket given — nothing was saved')166 }167 access_key, secret_key := s3_keys() or { return fail(err.msg()) }168 settings := machine.RemoteSettings{169 kind: machine.kind_s3170 endpoint: endpoint171 bucket: bucket172 region: region173 }174 return save_after_check(settings, Args{}, access_key, secret_key)175}176177178179fn s3_keys() !(string, string) {180 access_key := os.getenv('KOH_ACCESS_KEY')181 secret_key := os.getenv('KOH_SECRET_KEY')182 if access_key != '' && secret_key != '' {183 return access_key, secret_key184 }185 if !interactive() {186 return error('S3 keys are missing — set KOH_ACCESS_KEY and KOH_SECRET_KEY, or run koh remote on a terminal')187 }188 print(' access key: ')189 access_key_typed := os.get_line().trim_space()190 print(' secret key: ')191 secret_key_typed := os.get_line().trim_space()192 if access_key_typed == '' || secret_key_typed == '' {193 return error('both keys are needed — nothing was saved')194 }195 return access_key_typed, secret_key_typed196}197198fn describe(settings machine.RemoteSettings) string {199 if settings.kind == machine.kind_folder {200 return 'folder ${settings.path}'201 }202 return 's3 ${settings.endpoint}/${settings.bucket} (${settings.region})'203}204205fn or_default(value string, fallback string) string {206 return if value == '' { fallback } else { value }207}208209struct RemoteJson {210 configured bool211 kind string212 target string213}