2015-06-09 20:35:02 +03:00
|
|
|
:: digital ocean fleet management
|
|
|
|
::
|
|
|
|
::::
|
|
|
|
::
|
|
|
|
/? 314
|
2015-09-01 23:56:42 +03:00
|
|
|
/- talk
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
|
|
|
::
|
|
|
|
:::: sivtyv-barnel
|
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: data structures ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
!:
|
2015-09-01 23:56:42 +03:00
|
|
|
[talk .]
|
2015-06-09 20:35:02 +03:00
|
|
|
|%
|
|
|
|
++ instance
|
2015-12-20 23:50:45 +03:00
|
|
|
$: plat/?($do $gce)
|
|
|
|
name/@t
|
|
|
|
id/@t
|
|
|
|
status/@t
|
|
|
|
created/@da
|
|
|
|
snapshot/name/@t
|
2015-12-15 03:45:52 +03:00
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
++ image
|
2015-12-20 23:50:45 +03:00
|
|
|
$: plat/?($do $gce)
|
|
|
|
name/@t
|
|
|
|
id/@t
|
2015-12-15 03:45:52 +03:00
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
++ create-req-do
|
2015-12-20 23:50:45 +03:00
|
|
|
$: name/@t
|
|
|
|
size/@t
|
|
|
|
image/@t
|
|
|
|
ssh/(list cord)
|
|
|
|
backups/(unit ?)
|
2015-12-15 03:45:52 +03:00
|
|
|
ipv6+(unit ?)
|
2015-12-20 23:50:45 +03:00
|
|
|
private-networking/(unit ?)
|
|
|
|
user-data/(unit @t)
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
2015-12-20 23:50:45 +03:00
|
|
|
++ create-req-gce {project/@t zone/@t name/@t machine-type/@t}
|
2015-06-09 20:35:02 +03:00
|
|
|
++ axle
|
2015-12-20 23:50:45 +03:00
|
|
|
$: auth/{do/keys gce/keys}
|
|
|
|
toke/{do/tokens gce/tokens}
|
|
|
|
insts/(map @t instance)
|
|
|
|
images/(map {@t @t} image)
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
2015-12-20 23:50:45 +03:00
|
|
|
++ keys {authc/(unit @t) client-secret/(unit @t)}
|
|
|
|
++ tokens {access/@t refresh/@t}
|
2015-12-15 03:45:52 +03:00
|
|
|
++ move {bone card}
|
2015-06-09 20:35:02 +03:00
|
|
|
++ card
|
2015-12-15 03:45:52 +03:00
|
|
|
$% {$diff $json json}
|
|
|
|
{$wait wire @da}
|
|
|
|
{$send wire {ship term} $poke $talk-command command}
|
|
|
|
{$them wire (unit hiss)}
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
2015-06-10 23:53:43 +03:00
|
|
|
++ droplet-action
|
2015-12-15 03:45:52 +03:00
|
|
|
$% {$start $~}
|
|
|
|
{$stop $~}
|
|
|
|
{$reboot $~}
|
|
|
|
{$delete $~}
|
2015-12-20 23:50:45 +03:00
|
|
|
{$snapshot p/@t}
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
2015-06-10 23:53:43 +03:00
|
|
|
++ cloud-command
|
2015-12-20 23:50:45 +03:00
|
|
|
$% {$action id/@t name/@t act/droplet-action}
|
|
|
|
{$create-do p/json}
|
|
|
|
{$create-gce p/json}
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
--
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: miscellaneous functions ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
!:
|
2015-06-11 03:01:23 +03:00
|
|
|
|%
|
|
|
|
++ auth-queries
|
2015-12-20 23:50:45 +03:00
|
|
|
|= code/cord
|
2015-06-11 03:01:23 +03:00
|
|
|
:~ 'grant_type'^'authorization_code'
|
|
|
|
'code'^code
|
|
|
|
:- 'client_id'
|
|
|
|
'd8f46b95af38c1ab3d78ad34c2157a6959c23eb0eb5d8e393f650f08e6a75c6f'
|
2015-12-20 14:48:17 +03:00
|
|
|
'redirect_uri'^'http://localhost:8443/home#pub#cloud#fab'
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-iso8601
|
|
|
|
=< (cook to-time (parsf ;"{parse-day}T{parse-seconds}{parse-zone}"))
|
|
|
|
|%
|
|
|
|
++ to-time
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {{y/@u m/@u d/@u} t/{h/@u m/@u s/@u ms/@u} {syn/? zh/@u zm/@u}}
|
2015-06-09 20:35:02 +03:00
|
|
|
^- @da
|
|
|
|
%- year
|
|
|
|
^- date
|
|
|
|
=: h.t ?:(syn (sub h.t zh) (add h.t zh))
|
|
|
|
m.t ?:(syn (sub m.t zm) (add m.t zm))
|
|
|
|
==
|
|
|
|
[[& y] m d h.t m.t s.t (div (mul ms.t 0x1.0000) 1.000) ~]
|
|
|
|
++ parse-day (parsf ;"{dem}\-{dem}\-{dem}")
|
|
|
|
++ parse-seconds (parsf ;"{dem}:{dem}:{dem}{(optional ;~(pfix dot dem))}")
|
2015-12-20 23:50:45 +03:00
|
|
|
++ optional |*(fel/rule ;~(pose fel (easy 0)))
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-zone
|
|
|
|
;~ pose
|
|
|
|
(cold [& 0 0] (jest 'Z'))
|
|
|
|
(parsf ;"{parse-zone-sign}{dem}:{dem}")
|
|
|
|
==
|
|
|
|
++ parse-zone-sign ;~(plug ;~(pose (cold & lus) (cold | hep)))
|
|
|
|
--
|
2015-06-10 23:53:43 +03:00
|
|
|
++ parse-cloud-command
|
|
|
|
=+ jo
|
|
|
|
%- of :~
|
|
|
|
[%create-gce some]
|
2015-06-11 23:03:49 +03:00
|
|
|
[%create-do some]
|
|
|
|
::[%create-gce some]
|
2015-06-10 23:53:43 +03:00
|
|
|
:- %action
|
2015-12-20 14:48:17 +03:00
|
|
|
(ot id#so name#so act#parse-droplet-action ~)
|
2015-06-10 23:53:43 +03:00
|
|
|
==
|
|
|
|
++ parse-droplet-action
|
|
|
|
=> jo
|
|
|
|
%- of :~
|
|
|
|
[%start ul]
|
|
|
|
[%stop ul]
|
|
|
|
[%reboot ul]
|
|
|
|
[%delete ul]
|
|
|
|
[%snapshot so]
|
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
++ key-do
|
|
|
|
(mo [%start 'power_on'] [%stop 'shutdown'] [%reboot 'power_cycle'] ~)
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ adapter-do
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/cord
|
2015-06-09 20:35:02 +03:00
|
|
|
(~(got by key-do) a)
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-ip-do
|
|
|
|
=> jo
|
|
|
|
%- ot
|
|
|
|
:_ ~ v4/(ar (ot 'ip_address'^(su lip:ag) ~))
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-ip-gce
|
|
|
|
=> jo
|
2015-12-20 23:50:45 +03:00
|
|
|
%+ cu |=(a/(list (list @if)) `(list @if)`(zing a))
|
2015-06-09 20:35:02 +03:00
|
|
|
(ar (ot 'accessConfigs'^(ar (ot 'natIP'^(su lip:ag) ~)) ~))
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ tail-url
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/cord
|
2015-06-09 20:35:02 +03:00
|
|
|
-:(flop q.q:(need (epur a)))
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-region
|
|
|
|
=> jo
|
2015-12-20 14:48:17 +03:00
|
|
|
(ot name#so ~)
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ parse-id-text
|
2015-12-20 23:50:45 +03:00
|
|
|
|= jon/json
|
2015-12-15 03:45:52 +03:00
|
|
|
?.(?=({?($n $s) *} jon) ~ (some p.jon))
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ create-do-body
|
2015-12-20 23:50:45 +03:00
|
|
|
|= $: name/@t
|
|
|
|
size/@t
|
|
|
|
image/@t
|
|
|
|
ssh-keys/(list cord)
|
|
|
|
backups/(unit ?)
|
2015-12-15 03:45:52 +03:00
|
|
|
ipv6+(unit ?)
|
2015-12-20 23:50:45 +03:00
|
|
|
private-networking/(unit ?)
|
|
|
|
user-data/(unit @t)
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
|
|
|
%- jobe
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#s#name
|
|
|
|
size#s#size
|
|
|
|
image#s#image
|
|
|
|
backups#?~(backups ~ b#u.backups)
|
|
|
|
ipv6/?~(ipv6 ~ b#u.ipv6)
|
|
|
|
'user_data'^?~(user-data ~ s#u.user-data)
|
|
|
|
'private_networking'^?~(private-networking ~ b#u.private-networking)
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ convert-do
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/?($start $stop $reboot $snapshot)
|
2015-06-09 20:35:02 +03:00
|
|
|
?- a
|
2015-12-15 03:45:52 +03:00
|
|
|
$start
|
2015-06-09 20:35:02 +03:00
|
|
|
'power_on'
|
2015-12-15 03:45:52 +03:00
|
|
|
$stop
|
2015-06-09 20:35:02 +03:00
|
|
|
'shutdown'
|
2015-12-15 03:45:52 +03:00
|
|
|
$reboot
|
2015-06-09 20:35:02 +03:00
|
|
|
'power_cycle'
|
2015-12-15 03:45:52 +03:00
|
|
|
$snapshot
|
2015-06-10 23:53:43 +03:00
|
|
|
'snapshot'
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ instance-to-json
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/(list instance)
|
2015-06-10 23:53:43 +03:00
|
|
|
^- json
|
|
|
|
%+ joba 'instances'
|
2015-06-09 20:35:02 +03:00
|
|
|
:- %a
|
|
|
|
%+ turn a
|
|
|
|
|= instance
|
|
|
|
^- json
|
|
|
|
%- jobe
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#`json`s#name
|
|
|
|
id#s#id
|
|
|
|
status#s#status
|
|
|
|
created#s#(crip (dust (yore created)))
|
|
|
|
snapshot#s#snapshot
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
|
|
|
++ map-to-list
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/(map {@t @t} image)
|
|
|
|
^- liz/(list image)
|
2015-12-15 03:45:52 +03:00
|
|
|
%+ turn (~(tap by a) *(list {{@t @t} image}))
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{{@t @t} image} `image`+.a)
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ image-to-json
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/(list image)
|
2015-06-10 23:53:43 +03:00
|
|
|
%+ joba 'images'
|
2015-06-09 20:35:02 +03:00
|
|
|
:- %a
|
|
|
|
%+ turn a
|
|
|
|
|= image
|
|
|
|
^- json
|
|
|
|
%- jobe
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#s#name id#s#id ==
|
2015-06-09 20:35:02 +03:00
|
|
|
--
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::
|
|
|
|
:: main door ::
|
|
|
|
::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
!:
|
2015-12-20 23:50:45 +03:00
|
|
|
|_ {bowl vat/axle}
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: miscellaneous arms that have to be in main door for scope reasons ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
::
|
|
|
|
++ thou
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pour-path/path resp/?(httr *)}
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-12-20 14:48:17 +03:00
|
|
|
~& unhandled-pour-path#resp
|
2015-06-11 03:01:23 +03:00
|
|
|
:_ +>.$ ~
|
2015-12-15 03:45:52 +03:00
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
++ httpreq
|
2015-12-20 23:50:45 +03:00
|
|
|
|= $: pour-path/wire :: must be in main door because of scope
|
|
|
|
domain/(list cord)
|
|
|
|
end-point/path
|
|
|
|
req-type/$?($get $delt {$post json})
|
|
|
|
headers/math
|
|
|
|
queries/quay
|
2015-12-15 03:45:52 +03:00
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
^- move
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- parsed-url/purl
|
2015-06-09 20:35:02 +03:00
|
|
|
:+ ^= host-port :: ++hart
|
|
|
|
:+ security=%.y
|
|
|
|
port=~
|
|
|
|
host=[%.y [path=[%com domain]]]
|
|
|
|
endpoint=[extensions=~ point=end-point] :: ++pork,
|
|
|
|
q-strings=queries :: ++quay
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- request/hiss :: cast to hiss
|
2015-06-09 20:35:02 +03:00
|
|
|
:- parsed-url
|
|
|
|
?@ req-type
|
|
|
|
[req-type headers ~]
|
|
|
|
[%post headers ~ (tact (pojo +.req-type))]
|
|
|
|
:^ ost %them pour-path
|
|
|
|
`(unit hiss)`[~ request]
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: manage supscriptions and publish to talk ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
++ peer
|
2015-12-20 23:50:45 +03:00
|
|
|
|= pax/path
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-11 03:01:23 +03:00
|
|
|
:_ +>.$
|
|
|
|
=+ lis=(~(tap by insts.vat))
|
2015-12-20 23:50:45 +03:00
|
|
|
[ost %diff %json (instance-to-json (turn lis |=(a/{@t instance} +.a)))]~
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
++ spam
|
2015-12-20 23:50:45 +03:00
|
|
|
|= jon/json
|
2015-06-11 03:01:23 +03:00
|
|
|
%+ turn (~(tap by sup))
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {sub/bone @ pax/path}
|
2015-06-11 03:01:23 +03:00
|
|
|
^- move
|
|
|
|
[sub %diff %json jon]
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
++ publish
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {act/(list speech)}
|
2015-06-11 03:01:23 +03:00
|
|
|
^- move
|
|
|
|
=+ ^= spchz
|
|
|
|
%+ turn act
|
2015-12-20 23:50:45 +03:00
|
|
|
|= sp/speech
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ ^= tail
|
|
|
|
:- ^- audience
|
2015-12-20 00:00:01 +03:00
|
|
|
:+ :- `partner`[%& our ?+((clan our) !! $czar %court, $duke %porch)]
|
2015-06-11 03:01:23 +03:00
|
|
|
^- (pair envelope delivery)
|
|
|
|
[`envelope`[& ~] %pending]
|
|
|
|
~
|
|
|
|
~
|
|
|
|
`statement`[now ~ sp]
|
|
|
|
^- thought
|
|
|
|
:- `@`(sham eny tail)
|
|
|
|
tail
|
|
|
|
=+ mez=[%talk-command [%publish `(list thought)`spchz]]
|
|
|
|
[ost %send /pub [our %talk] %poke mez]
|
2015-12-20 00:00:01 +03:00
|
|
|
++ thou-pub |=($~ :_(+>.$ ~))
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: authentication ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ poke-cloud-auth
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {cde/cord typ/cord}
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
?: =(%do typ)
|
|
|
|
=. authc.do.auth.vat
|
|
|
|
[~ cde]
|
|
|
|
:_ +>.$ ~
|
|
|
|
=. access.gce.toke.vat
|
|
|
|
cde
|
|
|
|
:_ +>.$
|
|
|
|
:_ list-gce
|
|
|
|
(publish [%lin & 'successfully authenticated to gce']~)
|
|
|
|
::
|
|
|
|
++ poke-cloud-secret
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {secret/cord typ/cord}
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
?+ typ ~|(missing-platform=typ !!)
|
2015-12-20 00:00:01 +03:00
|
|
|
$do
|
2015-06-09 20:35:02 +03:00
|
|
|
=. client-secret.do.auth.vat
|
|
|
|
[~ secret]
|
|
|
|
:_ +>.$
|
|
|
|
:_ ~
|
2015-06-11 03:01:23 +03:00
|
|
|
%+ httpreq /do/auth
|
2015-06-09 20:35:02 +03:00
|
|
|
:^ ~[%digitalocean %cloud] `path`/v1/oauth/token
|
|
|
|
[%post ~]
|
|
|
|
:- ~ `quay`['client_secret'^secret (auth-queries (need authc.do.auth.vat))]
|
|
|
|
==
|
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
++ thou-do-auth
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {$~ resp/httr}
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
~| resp
|
|
|
|
=+ body=(rash q:(need r.resp) apex:poja)
|
2015-12-20 14:48:17 +03:00
|
|
|
~| receive-auth#resp(r body)
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ [ac re]=(need ((ot 'access_token'^so 'refresh_token'^so ~):jo body))
|
|
|
|
=: access.do.toke.vat ac
|
|
|
|
refresh.do.toke.vat re
|
|
|
|
==
|
|
|
|
:_ +>.$
|
|
|
|
:- (publish [%lin & 'successfully authenticated']~)
|
|
|
|
list-do
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: create digital ocean droplets ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ create-do
|
2015-12-20 23:50:45 +03:00
|
|
|
|= act/json
|
|
|
|
=+ ^- deets/create-req-do
|
2015-06-09 20:35:02 +03:00
|
|
|
%- need
|
|
|
|
%. act
|
|
|
|
=> jo
|
|
|
|
%- ot
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#so
|
|
|
|
size#so
|
|
|
|
image#so
|
|
|
|
ssh#(ar so)
|
|
|
|
backups#(mu bo)
|
2015-12-15 03:45:52 +03:00
|
|
|
'ipv6'^(mu bo)
|
|
|
|
'priv_networking'^(mu bo)
|
|
|
|
'user_data'^(mu so)
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- body/json
|
2015-12-15 03:45:52 +03:00
|
|
|
%- create-do-body
|
|
|
|
:* name.deets
|
|
|
|
size.deets
|
|
|
|
image.deets
|
|
|
|
ssh.deets
|
|
|
|
backups.deets
|
|
|
|
ipv6.deets
|
|
|
|
private-networking.deets
|
|
|
|
user-data.deets
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
|
|
|
%- httpreq :*
|
|
|
|
/create-do
|
|
|
|
~[%digitalocean %api] /v2/droplets
|
|
|
|
[%post body]
|
2015-12-20 14:48:17 +03:00
|
|
|
%^ mo ['Content-Type' 'application#json; charset=utf-8' ~]
|
2015-06-09 20:35:02 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.do.toke.vat) ~]
|
|
|
|
~
|
|
|
|
~
|
|
|
|
==
|
|
|
|
::
|
2015-12-20 23:50:45 +03:00
|
|
|
++ thou-create-do |=({path resp/httr} ~&(resp :_(+>.$ ~)))
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: create google instances ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-11 23:03:49 +03:00
|
|
|
++ reserve-ip
|
2015-12-20 23:50:45 +03:00
|
|
|
|= name/json
|
2015-12-20 14:48:17 +03:00
|
|
|
=+ nam=(need ((ot name#so ~):jo name))
|
2015-06-11 23:03:49 +03:00
|
|
|
%- httpreq
|
2015-06-12 00:30:48 +03:00
|
|
|
:* /reserve-ip/[nam]
|
2015-06-11 23:03:49 +03:00
|
|
|
~['googleapis' 'www']
|
|
|
|
/compute/v1/projects/urbcloud/regions/us-central1/addresses
|
2015-12-20 14:48:17 +03:00
|
|
|
[%post (joba name#s#nam)]
|
|
|
|
%^ mo ['Content-Type' 'application#json' ~]
|
2015-06-11 23:03:49 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.gce.toke.vat) ~]
|
|
|
|
~
|
|
|
|
*quay
|
|
|
|
==
|
|
|
|
::
|
|
|
|
++ thou-reserve-ip
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-06-12 00:30:48 +03:00
|
|
|
~& resp
|
|
|
|
~| r.resp
|
2015-06-11 23:03:49 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja)
|
2015-06-12 00:30:48 +03:00
|
|
|
=+ ur=(need ((ot 'targetLink'^so ~):jo parsed))
|
2015-12-20 14:48:17 +03:00
|
|
|
~& initial-response#parsed
|
2015-06-12 00:30:48 +03:00
|
|
|
=+ name=-:(flop q.q:(need (epur ur)))
|
2015-12-15 03:45:52 +03:00
|
|
|
=+ buf=`@da`(add ~s10 now)
|
|
|
|
:_(+>.$ [ost %wait `path`/check-ip-status/[name] buf]~)
|
2015-06-12 00:30:48 +03:00
|
|
|
::
|
|
|
|
++ wake-check-ip-status
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {name/path $~}
|
2015-12-20 14:48:17 +03:00
|
|
|
~& this-is-the-name#name
|
2015-06-12 00:30:48 +03:00
|
|
|
=+ nam=?~(name !! -.name)
|
|
|
|
:_ +>.$
|
|
|
|
:_ ~
|
|
|
|
%- httpreq
|
|
|
|
:* `path`/check-ip-status/[nam]
|
|
|
|
~['googleapis' 'www']
|
|
|
|
`path`/compute/v1/projects/urbcloud/regions/us-central1/addresses/[nam]
|
|
|
|
%get
|
2015-12-20 14:48:17 +03:00
|
|
|
%^ mo ['Content-Type' 'application#json' ~]
|
2015-06-12 00:30:48 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.gce.toke.vat) ~]
|
|
|
|
~
|
|
|
|
*quay
|
|
|
|
==
|
|
|
|
++ thou-check-ip-status
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {name/path resp/httr}
|
2015-12-20 14:48:17 +03:00
|
|
|
~& api-resp#resp
|
2015-06-12 00:30:48 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja)
|
|
|
|
!!
|
2015-12-20 14:48:17 +03:00
|
|
|
::?. =('RESERVED' (need ((ot status#so ~):jo parsed)))
|
2015-06-12 00:30:48 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ create-gce
|
2015-12-20 23:50:45 +03:00
|
|
|
|= jon/json
|
|
|
|
=+ ^- {name/@t image/@t number/@ud}
|
2015-12-20 14:48:17 +03:00
|
|
|
(need ((ot name#so 'instance_img'^so number#ni ~):jo jon))
|
2015-06-11 03:01:23 +03:00
|
|
|
|- ^- (list move)
|
|
|
|
?~ number ~
|
|
|
|
:_ $(number (dec number))
|
|
|
|
=+ nam=(cat 3 name (scot %ud number))
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- body/json
|
2015-06-09 20:35:02 +03:00
|
|
|
%- jobe
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#s#nam
|
|
|
|
'machineType'^s#'zones#us-central1-a#machineTypes#n1-standard-1'
|
2015-06-09 20:35:02 +03:00
|
|
|
:- %disks :- %a :_ ~
|
2015-06-11 03:01:23 +03:00
|
|
|
%- jobe
|
2015-12-20 14:48:17 +03:00
|
|
|
:+ 'initializeParams'^`json`(joba 'sourceImage'^s#image)
|
|
|
|
boot#b#%.y
|
2015-06-11 23:03:49 +03:00
|
|
|
~
|
2015-06-09 20:35:02 +03:00
|
|
|
:- 'networkInterfaces' :- %a :_ ~
|
2015-12-20 14:48:17 +03:00
|
|
|
(joba 'network' `json`[%s 'global#networks#default'])
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-06-11 03:01:23 +03:00
|
|
|
^- move
|
2015-06-09 20:35:02 +03:00
|
|
|
%- httpreq
|
|
|
|
:* `path`/create-gce
|
2015-12-15 03:45:52 +03:00
|
|
|
`(list cord)`~['googleapis' 'www']
|
|
|
|
`path`/compute/v1/projects/urbcloud/zones/us-central1-a/'instances'
|
2015-06-09 20:35:02 +03:00
|
|
|
[%post `json`body]
|
2015-12-20 14:48:17 +03:00
|
|
|
%^ mo ['Content-Type' 'application#json' ~]
|
2015-06-09 20:35:02 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.gce.toke.vat) ~]
|
|
|
|
~
|
|
|
|
`quay`[%key access.gce.toke.vat]~
|
|
|
|
==
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-12-20 23:50:45 +03:00
|
|
|
++ thou-create-gce |=({path resp/httr} ~&(resp :_(+>.$ ~)))
|
2015-06-11 03:01:23 +03:00
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: perform actions on instances (both kinds) ::
|
|
|
|
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
++ poke-json :: receive action from client
|
2015-12-20 23:50:45 +03:00
|
|
|
|= jon/json
|
2015-12-15 03:45:52 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ action=`cloud-command`(need (parse-cloud-command jon))
|
|
|
|
:_ +>.$
|
|
|
|
?- -.action
|
2015-12-15 03:45:52 +03:00
|
|
|
$create-gce [(reserve-ip p.action)]~
|
|
|
|
$create-do [(create-do p.action)]~
|
|
|
|
::$create-gce [(create-gce p.action)]
|
|
|
|
$action [(instance-action [id name act]:action)]~
|
2015-06-11 03:01:23 +03:00
|
|
|
==
|
|
|
|
++ instance-action
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {id/@t name/@t action/droplet-action}
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ d=(~(got by insts.vat) id)
|
|
|
|
~| 'can\'t find id'
|
|
|
|
=+ typ=?~(d !! -.d)
|
|
|
|
?- typ
|
2015-12-15 03:45:52 +03:00
|
|
|
$do
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ ^= meth
|
2015-12-20 00:00:01 +03:00
|
|
|
?: ?=($delete -.action)
|
2015-12-15 03:45:52 +03:00
|
|
|
%delt
|
|
|
|
:- %post
|
2015-12-20 14:48:17 +03:00
|
|
|
%+ jobe type#s#(convert-do -.action)
|
|
|
|
?.(?=($snapshot -.action) ~ [name#s#p.action ~])
|
2015-06-11 03:01:23 +03:00
|
|
|
^- move
|
|
|
|
=+ ^= req
|
|
|
|
%- httpreq :*
|
|
|
|
/do/[-.action]
|
|
|
|
~[%digitalocean %api]
|
2015-12-20 00:00:01 +03:00
|
|
|
?:(?=($delt meth) /v2/droplets/[id] /v2/droplets/[id]/actions)
|
2015-06-11 03:01:23 +03:00
|
|
|
meth
|
2015-12-20 14:48:17 +03:00
|
|
|
%^ mo ['Content-Type' 'application#json' ~]
|
2015-06-11 03:01:23 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.do.toke.vat) ~] ~
|
|
|
|
*quay
|
|
|
|
==
|
|
|
|
req
|
|
|
|
::
|
2015-12-15 03:45:52 +03:00
|
|
|
$gce
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ ^= head-query
|
2015-12-20 14:48:17 +03:00
|
|
|
:- %^ mo ['Content-Type' 'application#json' ~]
|
2015-06-11 03:01:23 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.gce.toke.vat) ~] ~
|
|
|
|
*quay
|
|
|
|
?- -.action
|
2015-12-15 03:45:52 +03:00
|
|
|
?($start $stop $reboot $'snapshot')
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ end=/compute/v1/projects/urbcloud/zones/us-central1-a/instances/[name]
|
|
|
|
%- httpreq
|
|
|
|
:* /gce-act/[-.action] ~['googleapis' 'www']
|
2015-12-20 00:00:01 +03:00
|
|
|
(welp end [?:(?=($reboot -.action) 'reset' -.action) ~])
|
2015-06-11 03:01:23 +03:00
|
|
|
[%post ~]
|
|
|
|
head-query
|
|
|
|
==
|
|
|
|
::
|
2015-12-15 03:45:52 +03:00
|
|
|
$delete
|
2015-06-11 03:01:23 +03:00
|
|
|
=+ end=/compute/v1/projects/urbcloud/zones/us-central1-a/instances/[name]
|
|
|
|
%- httpreq
|
|
|
|
:* /gce-act/[-.action] ~['googleapis' 'www']
|
|
|
|
end
|
|
|
|
%delt
|
|
|
|
head-query
|
|
|
|
==
|
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-06-11 03:01:23 +03:00
|
|
|
++ thou-do-act
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-12-20 14:48:17 +03:00
|
|
|
~& [resp act#pax]
|
2015-06-11 03:01:23 +03:00
|
|
|
:_ +>.$ ~
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
2015-06-11 03:01:23 +03:00
|
|
|
++ thou-gce-act
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-12-20 14:48:17 +03:00
|
|
|
~& [resp act#pax]
|
2015-06-11 03:01:23 +03:00
|
|
|
:_ +>.$ ~
|
|
|
|
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: retrieve google instances and images ::
|
|
|
|
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ list-gce
|
|
|
|
^- (list move)
|
|
|
|
:+ (list-something-gce /zones/['us-central1-a']/instances)
|
|
|
|
(list-something-gce /global/snapshots)
|
|
|
|
~
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ list-something-gce
|
2015-12-20 23:50:45 +03:00
|
|
|
|= endpoint/path
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ ^= lis
|
|
|
|
:*
|
|
|
|
/list-gce/[-.endpoint]
|
|
|
|
~[%googleapis %www] (welp /compute/v1/projects/urbcloud endpoint)
|
|
|
|
%get ~
|
|
|
|
^- quay
|
|
|
|
[%'access_token' access.gce.toke.vat]~
|
|
|
|
==
|
|
|
|
~! lis
|
|
|
|
~! +<:httpreq
|
|
|
|
(httpreq lis)
|
|
|
|
::
|
|
|
|
++ thou-list-gce-zones :: instances
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-12-20 00:00:01 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja) :: body httr to json
|
|
|
|
~| 'no list received or bad json'
|
2015-12-20 14:48:17 +03:00
|
|
|
=+ items=(need ((ot items#(ar some) ~):jo parsed))
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- ins/(list {@t instance})
|
2015-06-09 20:35:02 +03:00
|
|
|
~| 'bad-json'^items
|
|
|
|
%+ turn items
|
2015-12-20 23:50:45 +03:00
|
|
|
|= in/json
|
2015-06-09 20:35:02 +03:00
|
|
|
=< [id .]
|
|
|
|
^- instance
|
|
|
|
:- %gce
|
|
|
|
%- need
|
|
|
|
%. in =+ jo
|
|
|
|
%- ot
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#so
|
|
|
|
id#so
|
|
|
|
status#so
|
|
|
|
'creationTimestamp'^(su parse-iso8601) ::zone#so
|
2015-06-09 20:35:02 +03:00
|
|
|
'machineType'^(cu tail-url so)
|
2015-12-18 04:16:57 +03:00
|
|
|
:: 'networkInterfaces'^parse-ip-gce
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
|
|
|
=. insts.vat
|
|
|
|
%- mo
|
|
|
|
%+ weld ins
|
|
|
|
%+ skip (~(tap by insts.vat)) :: keep non-gce
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{@t instance} ?=($gce +<.a))
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ buf=`@da`(add ~s10 now)
|
|
|
|
=+ liz=(~(tap by insts.vat))
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ tail=(turn liz |=(a/{@t instance} +.a))
|
2015-06-09 20:35:02 +03:00
|
|
|
:_ +>.$ ::
|
|
|
|
:- [ost %wait /refresh-gce buf]
|
|
|
|
(spam (instance-to-json tail))
|
|
|
|
::
|
|
|
|
++ thou-list-gce-global :: imgs
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-12-18 04:16:57 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja)
|
2015-12-20 14:48:17 +03:00
|
|
|
=+ imgz=(need ((ot items#(ar some) ~):jo parsed))
|
2015-06-09 20:35:02 +03:00
|
|
|
=. images.vat
|
|
|
|
%- mo
|
|
|
|
%+ weld
|
2015-12-18 04:16:57 +03:00
|
|
|
%+ skip (~(tap by images.vat) *(list {{@t @t} image}))
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{{@t @t} image} ?=($gce ->.a))
|
2015-06-09 20:35:02 +03:00
|
|
|
%+ turn imgz
|
2015-12-20 23:50:45 +03:00
|
|
|
|= a/json
|
2015-06-09 20:35:02 +03:00
|
|
|
=< [[name %gce] .]
|
|
|
|
^- image
|
|
|
|
:- %gce
|
|
|
|
%- need
|
|
|
|
%. a =+ jo
|
|
|
|
%- ot
|
2015-12-20 14:48:17 +03:00
|
|
|
[name#so id#so ~]
|
2015-06-09 20:35:02 +03:00
|
|
|
:_ +>.$ [(spam `json`(image-to-json `(list image)`(map-to-list images.vat)))]
|
2015-06-11 03:01:23 +03:00
|
|
|
::
|
2015-12-18 04:16:57 +03:00
|
|
|
++ wake-refresh-gce |=({path $~} [list-gce +>.$])
|
2015-06-11 03:01:23 +03:00
|
|
|
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
:: list digital ocean droplets and images ::
|
|
|
|
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
2015-06-09 20:35:02 +03:00
|
|
|
++ list-do
|
2015-12-20 00:00:01 +03:00
|
|
|
:+((list-something-do %droplets) (list-something-do %images) ~)
|
2015-06-09 20:35:02 +03:00
|
|
|
++ list-something-do
|
2015-12-20 23:50:45 +03:00
|
|
|
|= som/@tas
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ ^= lis
|
|
|
|
:~ /list-do/[som]
|
2015-12-20 00:00:01 +03:00
|
|
|
~[%digitalocean %api]
|
|
|
|
/v2/[som]
|
|
|
|
%get
|
|
|
|
%- mo
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ ['Content-Type' 'application#json' ~]
|
2015-12-20 00:00:01 +03:00
|
|
|
['Authorization' (cat 3 'Bearer ' access.do.toke.vat) ~]
|
|
|
|
==
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
2015-12-20 00:00:01 +03:00
|
|
|
(httpreq lis)
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
|
|
|
++ thou-list-do-droplets
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-12-18 04:16:57 +03:00
|
|
|
^- {(list move) _+>.$}
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja) :: parse httr to json
|
2015-12-20 14:48:17 +03:00
|
|
|
~| receive-list#parsed
|
|
|
|
=+ dar=(need ((ot droplets#(ar some) ~):jo parsed)) :: reparse ar of insts
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- dropz/(list {@t instance})
|
2015-12-20 14:48:17 +03:00
|
|
|
~| bad-json#-.dar
|
2015-06-09 20:35:02 +03:00
|
|
|
%+ turn dar
|
2015-12-20 23:50:45 +03:00
|
|
|
|= drp/json ^- {@t instance}
|
2015-06-09 20:35:02 +03:00
|
|
|
=- ~! - -
|
|
|
|
=< [id .]
|
|
|
|
^- instance
|
|
|
|
:- %do
|
|
|
|
%- need
|
|
|
|
%. drp
|
|
|
|
=+ jo
|
|
|
|
%- ot
|
2015-12-20 14:48:17 +03:00
|
|
|
:~ name#so
|
|
|
|
id#parse-id-text
|
|
|
|
status#so
|
2015-12-18 04:16:57 +03:00
|
|
|
'created_at'^(su parse-iso8601)
|
2015-12-20 14:48:17 +03:00
|
|
|
image#(ot name#so ~)
|
2015-06-09 20:35:02 +03:00
|
|
|
==
|
|
|
|
=. insts.vat
|
|
|
|
%- mo
|
|
|
|
%+ weld dropz
|
2015-12-19 04:24:34 +03:00
|
|
|
%+ skip (~(tap by insts.vat) *(list {@t instance}))
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{@t instance} ?=($do +>.$))
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ buf=`@da`(add ~s10 now)
|
|
|
|
:_ +>.$
|
|
|
|
:- [ost %wait /refresh-do buf]
|
|
|
|
%- spam
|
|
|
|
%- instance-to-json
|
2015-12-19 04:24:34 +03:00
|
|
|
%+ turn (~(tap by insts.vat) *(list {@t instance}))
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{@t instance} +.a)
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
|
|
|
++ thou-list-do-images
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {pax/path resp/httr}
|
2015-06-09 20:35:02 +03:00
|
|
|
=+ parsed=(rash q:(need r.resp) apex:poja)
|
2015-12-20 14:48:17 +03:00
|
|
|
~| crashed-do-images#parsed
|
2015-12-18 04:16:57 +03:00
|
|
|
=+ ^= imgz
|
|
|
|
%- need
|
2015-12-20 14:48:17 +03:00
|
|
|
((ot images#(ar (ot [name#so distribution#so id#no ~])) ~):jo parsed)
|
2015-12-20 23:50:45 +03:00
|
|
|
=+ ^- images/(list {{@t @t} image})
|
2015-06-09 20:35:02 +03:00
|
|
|
%+ turn imgz
|
2015-12-20 23:50:45 +03:00
|
|
|
|= {name/@t dist/@t id/@t}
|
2015-06-10 23:53:43 +03:00
|
|
|
=+ nom=(cat 3 name dist)
|
|
|
|
[[%do nom] `image`[%do nom id]]
|
2015-06-09 20:35:02 +03:00
|
|
|
=. images.vat
|
|
|
|
%- mo
|
|
|
|
%+ weld images
|
2015-12-20 00:00:01 +03:00
|
|
|
%+ skip (~(tap by images.vat) *(list {{@t @t} image}))
|
2015-12-20 23:50:45 +03:00
|
|
|
|=(a/{{@t @t} image} ?=($do ->.a))
|
2015-12-20 00:00:01 +03:00
|
|
|
:_ +>.$
|
|
|
|
~[(spam `json`(image-to-json `(list image)`(map-to-list images.vat)))]
|
2015-06-09 20:35:02 +03:00
|
|
|
::
|
2015-12-15 03:45:52 +03:00
|
|
|
++ wake-refresh-do |=({path $~} [list-do +>.$])
|
2015-06-09 20:35:02 +03:00
|
|
|
--
|