1 'use strict'; 2 3 import { readfile, writefile, unlink, mkdir, rmdir, lsdir, chmod } from 'fs'; 4 const ubus = require('ubus'); 5 6 const REG_DIR = '/tmp/run/uvol/.meta/uxc'; 7 const META_DIR = '/tmp/run/uvol/.meta'; 8 const STACK_DIR = '/usr/share/uxc/stacks'; 9 10 let action = ARGV[0]; 11 let app = ARGV[1]; 12 let hosts_file = null; 13 14 if (action != 'up' && action != 'down' || !app) 15 die('usage: uxc-stack <up|down> <app>'); 16 17 function read_json(path) { 18 let raw = readfile(path); 19 return raw ? json(raw) : null; 20 } 21 22 function uxc(...args) { 23 let rc = system([ 'uxc', ...args ]); 24 if (rc != 0) 25 printf('uxc-stack: uxc %s -> %d\n', join(' ', args), rc); 26 return rc; 27 } 28 29 function qname(name) { 30 return app + '.' + name; 31 } 32 33 34 let comp = { instances: [], secrets: {} }; 35 36 let api = { 37 instance: function(name, spec) { 38 spec ??= {}; 39 spec.name = name; 40 push(comp.instances, spec); 41 return spec; 42 }, 43 44 generate: function(scope) { 45 comp.secrets[scope] = true; 46 return 'generate@' + app + '/' + scope; 47 }, 48 }; 49 50 function compose_stack() { 51 let tmpl_path = STACK_DIR + '/' + app + '.uc'; 52 let prog = loadfile(tmpl_path); 53 if (!prog) 54 die('uxc-stack: cannot load stack template ' + tmpl_path); 55 let compose = prog(); 56 if (type(compose) != 'function') 57 die('uxc-stack: ' + tmpl_path + ' must return a compose(api) function'); 58 compose(api); 59 } 60 61 function stack_members() { 62 let out = []; 63 for (let e in (lsdir(REG_DIR) ?? [])) { 64 if (substr(e, -5) != '.json') 65 continue; 66 let reg = read_json(REG_DIR + '/' + e); 67 if (reg && reg.origin == 'stack:' + app) 68 push(out, reg); 69 } 70 return out; 71 } 72 73 74 function fnv1a(s) { 75 let h = 2166136261; 76 for (let i = 0; i < length(s); i++) { 77 h ^= ord(s, i); 78 h = (h * 16777619) & 0xffffffff; 79 } 80 return h; 81 } 82 83 function idmap_offset(qn) { 84 return ((fnv1a(qn) % 0x7000) + 1) * 0x10000; 85 } 86 87 function backhaul_net() { 88 let slot = fnv1a(app) % 512; 89 return sprintf('198.%d.%d', 18 + (slot >> 8), slot & 0xff); 90 } 91 92 function build_registration(inst) { 93 if (!inst.image) 94 die('uxc-stack: instance ' + inst.name + ' declares no image'); 95 96 let img = read_json(REG_DIR + '/' + inst.image + '.json'); 97 if (!img) 98 die('uxc-stack: image "' + inst.image + '" not installed (need container-' + inst.image + ')'); 99 100 let reg = { 101 name: qname(inst.name), 102 image: img.image, 103 'image-digest': img['image-digest'], 104 path: img.path, 105 origin: 'stack:' + app, 106 autostart: true, 107 }; 108 109 if (length(inst.volumes)) { 110 let vols = []; 111 for (let v in inst.volumes) { 112 let p = split(v, ':'); 113 push(vols, { name: p[0], mountpoint: p[1], size: p[2] }); 114 } 115 reg['data-volumes'] = vols; 116 } 117 if (inst.overlay) 118 reg['temp-overlay-size'] = inst.overlay; 119 else if (img['temp-overlay-size']) 120 reg['temp-overlay-size'] = img['temp-overlay-size']; 121 else if (img['overlay-size']) 122 reg['overlay-size'] = img['overlay-size']; 123 if (inst.env) 124 reg.initenv = inst.env; 125 126 let prov = []; 127 if (inst.provision || inst.secrets) { 128 let pdir = META_DIR + '/stacks/' + app + '/' + inst.name; 129 mkdir(META_DIR + '/stacks/' + app, 0700); 130 mkdir(pdir, 0700); 131 for (let dest in (inst.provision ?? {})) { 132 let src = pdir + '/' + replace(dest, /[^A-Za-z0-9._-]/g, '_'); 133 if (writefile(src, inst.provision[dest]) == null) 134 die('uxc-stack: cannot write provisioned file ' + src); 135 chmod(src, 0644); 136 push(prov, { source: src, destination: dest }); 137 } 138 for (let dest in (inst.secrets ?? {})) 139 push(prov, { source: META_DIR + '/secrets/' + app + '/' + 140 inst.secrets[dest] + '/value', destination: dest, secret: true }); 141 } 142 if (length(prov)) 143 reg.provision = prov; 144 145 reg['idmap-offset'] = sprintf('%d', idmap_offset(reg.name)); 146 147 if (hosts_file) 148 reg['hosts-file'] = hosts_file; 149 150 return reg; 151 } 152 153 function instance_up(inst) { 154 let reg = build_registration(inst); 155 let path = REG_DIR + '/' + reg.name + '.json'; 156 if (writefile(path, sprintf('%.J\n', reg)) == null) 157 die('uxc-stack: cannot write ' + path); 158 } 159 160 function bringup() { 161 ubus.call({ object: 'service', method: 'event', 162 data: { type: 'uxc.bringup', data: {} } }); 163 let err = ubus.error(); 164 if (err) 165 die('uxc-stack: cannot trigger bring-up: ' + err); 166 } 167 168 169 function backhaul_prepare() { 170 let net = backhaul_net(); 171 let hdir = META_DIR + '/stacks/' + app; 172 let hosts = '127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\n'; 173 let i = 0, ordered, inst, side; 174 175 ordered = sort(comp.instances, function(a, b) { 176 return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0; 177 }); 178 for (inst in ordered) { 179 inst.bh_address = net + '.' + (i + 1); 180 hosts += inst.bh_address + '\t' + inst.name + '\n'; 181 i++; 182 } 183 184 mkdir(META_DIR + '/stacks', 0700); 185 mkdir(hdir, 0700); 186 writefile(hdir + '/hosts', hosts); 187 hosts_file = hdir + '/hosts'; 188 189 for (inst in comp.instances) { 190 side = { 191 'org.openwrt.network.backhaul': app, 192 'org.openwrt.network.backhaul-address': inst.bh_address, 193 }; 194 if (inst.access) { 195 let f = split(inst.access, ' '); 196 side['org.openwrt.network.attach'] = f[0]; 197 for (let n = 1; n < length(f); n++) { 198 let kv = split(f[n], '='); 199 if (kv[0] == 'egress') 200 side['org.openwrt.network.egress'] = kv[1]; 201 else if (kv[0] == 'ingress') 202 side['org.openwrt.network.ingress'] = kv[1]; 203 else if (kv[0] == 'host') 204 side['org.openwrt.network.host'] = kv[1]; 205 } 206 } 207 if (inst.proto) 208 side['org.openwrt.network.proto'] = inst.proto; 209 if (inst.proto6) 210 side['org.openwrt.network.proto6'] = inst.proto6; 211 if (inst.ip6ifaceid) 212 side['org.openwrt.network.ip6ifaceid'] = inst.ip6ifaceid; 213 writefile(REG_DIR + '/' + qname(inst.name) + '.annotations', sprintf('%J\n', side)); 214 } 215 } 216 217 218 if (action == 'up') { 219 compose_stack(); 220 backhaul_prepare(); 221 for (let inst in comp.instances) 222 instance_up(inst); 223 bringup(); 224 } else { 225 let members = stack_members(); 226 let kept = []; 227 228 for (let reg in members) { 229 let krc = system([ 'uxc', 'kill', reg.name ]); 230 if (krc != 0 && krc != 254) 231 printf('uxc-stack: uxc kill %s -> %d\n', reg.name, krc); 232 uxc('delete', reg.name); 233 unlink(REG_DIR + '/' + reg.name + '.json'); 234 unlink(REG_DIR + '/' + reg.name + '.annotations'); 235 system([ 'rm', '-rf', META_DIR + '/uxc/state/' + reg.name ]); 236 for (let v in (reg['data-volumes'] ?? [])) 237 push(kept, reg.name + '.' + v.name); 238 } 239 240 unlink(META_DIR + '/stacks/' + app + '/hosts'); 241 rmdir(META_DIR + '/stacks/' + app); 242 243 let secrets = lsdir(META_DIR + '/secrets/' + app) ?? []; 244 245 if (length(kept)) { 246 warn('uxc-stack: kept data volume(s) for ' + app + 247 '; remove manually if the data is no longer needed:\n'); 248 for (let vn in kept) 249 warn(' uvol remove ' + vn + '\n'); 250 } 251 if (length(secrets)) { 252 warn('uxc-stack: kept generated secret(s) for ' + app + 253 ' (paired with the data above; reused on reinstall); remove with:\n'); 254 warn(' rm -rf ' + META_DIR + '/secrets/' + app + '\n'); 255 } 256 }
This page was automatically generated by LXR 0.3.1. • OpenWrt