mirror of
https://github.com/daylinmorgan/oizys.git
synced 2024-12-24 16:00:45 -06:00
Compare commits
No commits in common. "c450271582299d04e9f785a3023b1691be03b672" and "908ab5189a7ddb75b903aa14bda416d8c8d478ef" have entirely different histories.
c450271582
...
908ab5189a
8 changed files with 51 additions and 219 deletions
34
flake.lock
34
flake.lock
|
@ -323,11 +323,11 @@
|
||||||
"xdph": "xdph"
|
"xdph": "xdph"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725976150,
|
"lastModified": 1725873008,
|
||||||
"narHash": "sha256-Dv4XEWRcVFZhBDbj11/zuuXyf7TGHFVU1IGH9W/yPX8=",
|
"narHash": "sha256-hVPjlB0EPbf98tm4LcwOmS80tO/qfAoXqXKWZjDUG50=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "155d44016d0cb11332c454db73d59030cdbd7b13",
|
"rev": "04421063af2941c6e27e6dca2bdc2c387778a3a5",
|
||||||
"revCount": 5209,
|
"revCount": 5203,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/hyprwm/Hyprland/"
|
"url": "https://github.com/hyprwm/Hyprland/"
|
||||||
|
@ -504,11 +504,11 @@
|
||||||
"lix": {
|
"lix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725927421,
|
"lastModified": 1725846500,
|
||||||
"narHash": "sha256-tiQ9OxiuTb/02xEU2ceo9MIxWBS5Rm/IAhv6QshH8K0=",
|
"narHash": "sha256-8tzJO3PllVPc0RYE0OfXVWlgTiJxKH1nzXsQLGyFRJ4=",
|
||||||
"rev": "cc183fdbc14ce105a5661d646983f791978b9d5c",
|
"rev": "c14486ae8d3bbc862c625d948a6b2f4dc0927d5b",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/cc183fdbc14ce105a5661d646983f791978b9d5c.tar.gz?rev=cc183fdbc14ce105a5661d646983f791978b9d5c"
|
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/c14486ae8d3bbc862c625d948a6b2f4dc0927d5b.tar.gz?rev=c14486ae8d3bbc862c625d948a6b2f4dc0927d5b"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
|
@ -744,11 +744,11 @@
|
||||||
"nixpkgs": "nixpkgs_6"
|
"nixpkgs": "nixpkgs_6"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725981791,
|
"lastModified": 1725885379,
|
||||||
"narHash": "sha256-+4dwaoIrnubM29MK8BW4S2mdKwdlCF1svtO0hQ443X0=",
|
"narHash": "sha256-gw+CYMQRqzErAIp4WOTTeX6YXOhgk9YWyTM1Sa2cACA=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs-wayland",
|
"repo": "nixpkgs-wayland",
|
||||||
"rev": "90046312d6c074e7b941b7ea9c4e54f4d416e5da",
|
"rev": "dc951da2b8fdaabc09a1bc72dd5744438976be47",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -900,11 +900,11 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725957803,
|
"lastModified": 1725679186,
|
||||||
"narHash": "sha256-qBG8DEmc9aOLr/WBtsuOB5QKEDxK2bDy4dq3X686xdo=",
|
"narHash": "sha256-Eq+AI70CYMpIAhCjJ0VOoafd3tVhRYgXi8CzEqDn0KI=",
|
||||||
"owner": "roc-lang",
|
"owner": "roc-lang",
|
||||||
"repo": "roc",
|
"repo": "roc",
|
||||||
"rev": "2936a37a1c54cb4cb10003c3b7e43a4772bbccf9",
|
"rev": "9a4d55672551fb4ffb54983272bb02d119c19f85",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -1162,11 +1162,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725228143,
|
"lastModified": 1725203932,
|
||||||
"narHash": "sha256-kbSiPA5oXiz1+1eVoRslMi5wylHD6SDT8dS9eZAxXAM=",
|
"narHash": "sha256-VLULC/OnI+6R9KEP2OIGk+uLJJsfRlaLouZ5gyFd2+Y=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "xdg-desktop-portal-hyprland",
|
"repo": "xdg-desktop-portal-hyprland",
|
||||||
"rev": "11e15b437e7efc39e452f36e15a183225d6bfa39",
|
"rev": "2425e8f541525fa7409d9f26a8ffaf92a3767251",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
## nix begat oizys
|
## nix begat oizys
|
||||||
import std/[os, tables, sequtils, strformat,strutils]
|
import std/[os, tables, sequtils, strformat,]
|
||||||
|
|
||||||
import cligen, bbansi
|
import cligen, bbansi
|
||||||
import oizys/[context, github, nix, overlay, logging]
|
import oizys/[context, github, nix, overlay, logging]
|
||||||
|
@ -13,17 +13,6 @@ addHandler(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
proc confirm(q: string): bool =
|
|
||||||
stderr.write $(q & bb"[yellow] (Y/n) ")
|
|
||||||
while true:
|
|
||||||
let ans = readLine(stdin)
|
|
||||||
case ans.strip().toLowerAscii():
|
|
||||||
of "y","yes": return true
|
|
||||||
of "n","no": return false
|
|
||||||
else:
|
|
||||||
stderr.write($bb("[red]Please answer Yes/no\nexpected one of [b]Y,yes,N,no "))
|
|
||||||
stderr.write "\n"
|
|
||||||
|
|
||||||
overlay:
|
overlay:
|
||||||
proc pre(
|
proc pre(
|
||||||
flake: string = "",
|
flake: string = "",
|
||||||
|
@ -43,20 +32,9 @@ overlay:
|
||||||
## output
|
## output
|
||||||
echo nixosConfigAttrs().join(" ")
|
echo nixosConfigAttrs().join(" ")
|
||||||
|
|
||||||
proc update(
|
proc update(yes: bool = false) =
|
||||||
yes: bool = false,
|
## *TBI* update and run nixos-rebuild
|
||||||
preview: bool = false
|
fatal "not implemented"
|
||||||
) =
|
|
||||||
## update and run nixos-rebuild
|
|
||||||
let hosts = getHosts()
|
|
||||||
if hosts.len > 1: fatalQuit "operation only supports one host"
|
|
||||||
let run = getLastUpdateRun()
|
|
||||||
echo fmt"run created at: {run.created_at}"
|
|
||||||
echo "nvd diff:\n", getUpdateSummary(run.id, hosts[0])
|
|
||||||
if preview: quit 0
|
|
||||||
if yes or confirm("Proceed with system update?"):
|
|
||||||
updateRepo()
|
|
||||||
nixosRebuild("switch")
|
|
||||||
|
|
||||||
proc build(minimal: bool = false) =
|
proc build(minimal: bool = false) =
|
||||||
## nix build
|
## nix build
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import std/[logging, os, strformat, strutils]
|
import std/[logging, os, strformat, strutils]
|
||||||
from std/nativesockets import getHostname
|
from std/nativesockets import getHostname
|
||||||
import bbansi
|
|
||||||
import ./logging
|
import ./logging
|
||||||
|
|
||||||
type
|
type
|
||||||
OizysContext* = object
|
OizysContext* = object
|
||||||
flake: string
|
flake, host: string
|
||||||
hosts: seq[string]
|
hosts: seq[string]
|
||||||
debug: bool
|
debug: bool
|
||||||
ci: bool
|
ci: bool
|
||||||
|
@ -39,12 +39,13 @@ proc updateContext*(
|
||||||
) =
|
) =
|
||||||
oc.debug = debug
|
oc.debug = debug
|
||||||
oc.resetCache = resetCache
|
oc.resetCache = resetCache
|
||||||
|
if host.len > 0:
|
||||||
|
oc.hosts = host
|
||||||
if flake != "":
|
if flake != "":
|
||||||
oc.flake =
|
oc.flake =
|
||||||
if flake.startsWith("github") or flake.startsWith("git+"): flake
|
if flake.startsWith("github") or flake.startsWith("git+"): flake
|
||||||
else: checkPath(flake.normalizedPath().absolutePath())
|
else: checkPath(flake.normalizedPath().absolutePath())
|
||||||
|
debug oc
|
||||||
debug bb(fmt"""[b]flake[/]: {oc.flake}, [b]hosts[/]: {oc.hosts.join(" ")}""")
|
|
||||||
|
|
||||||
proc getHosts*(): seq[string] = return oc.hosts
|
proc getHosts*(): seq[string] = return oc.hosts
|
||||||
proc getFlake*(): string = return oc.flake
|
proc getFlake*(): string = return oc.flake
|
||||||
|
|
|
@ -23,7 +23,7 @@ type
|
||||||
|
|
||||||
proc runCmdCapt*(
|
proc runCmdCapt*(
|
||||||
cmd: string,
|
cmd: string,
|
||||||
capture: set[CaptureGrp] = {CaptStdout},
|
capture: set[CaptureGrp],
|
||||||
): tuple[stdout, stderr: string, exitCode: int] =
|
): tuple[stdout, stderr: string, exitCode: int] =
|
||||||
debug fmt"running cmd: {cmd}"
|
debug fmt"running cmd: {cmd}"
|
||||||
let args = cmd.splitWhitespace()
|
let args = cmd.splitWhitespace()
|
||||||
|
|
|
@ -1,43 +1,21 @@
|
||||||
import std/[httpclient,logging, os, strformat, strutils, json, tables, tempfiles]
|
import std/[httpclient,logging, os, strformat, strutils, json]
|
||||||
import jsony, bbansi, zippy/ziparchives
|
import ./logging
|
||||||
import ./[logging, exec, context]
|
|
||||||
|
|
||||||
# localPassC is used by zippy but the additional
|
|
||||||
# module mangling on nixos somehow breaks localPassC
|
|
||||||
when defined(amd64) and (defined(gcc) or defined(clang)):
|
|
||||||
{.passC: "-msse4.1 -mpclmul".}
|
|
||||||
|
|
||||||
template withTmpDir(body: untyped): untyped =
|
|
||||||
let tmpDir {.inject.} = createTempDir("oizys","")
|
|
||||||
body
|
|
||||||
removeDir(tmpDir)
|
|
||||||
|
|
||||||
var ghToken = getEnv("GITHUB_TOKEN")
|
var ghToken = getEnv("GITHUB_TOKEN")
|
||||||
|
|
||||||
proc checkToken() {.inline.} =
|
proc checkToken() {.inline.} =
|
||||||
if ghToken == "": fatalQuit "GITHUB_TOKEN not set"
|
if ghToken == "": fatalQuit "GITHUB_TOKEN not set"
|
||||||
|
|
||||||
proc ghClient(
|
#[curl -L \
|
||||||
maxRedirects = 5
|
-X POST \
|
||||||
): HttpClient =
|
-H "Accept: application/vnd.github+json" \
|
||||||
checkToken()
|
-H "Authorization: Bearer <YOUR-TOKEN>" \
|
||||||
result = newHttpClient(maxRedirects = maxRedirects)
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
result.headers = newHttpHeaders({
|
https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \
|
||||||
"Accept" : "application/vnd.github+json",
|
-d '{"ref":"topic-branch","inputs":{"name":"Mona the Octocat","home":"San Francisco, CA"}}'
|
||||||
"Authorization" : fmt"Bearer {ghToken}",
|
]#
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
proc getGhApi(url: string): Response =
|
|
||||||
let client = ghClient()
|
|
||||||
try:
|
|
||||||
result = client.get(url)
|
|
||||||
except:
|
|
||||||
error fmt"github api request failed: {url}"
|
|
||||||
error fmt"response: {result.body}"
|
|
||||||
quit QuitFailure
|
|
||||||
|
|
||||||
proc postGhApi(url: string, body: JsonNode) =
|
proc postGhApi(url: string, body: JsonNode) =
|
||||||
checkToken()
|
checkToken()
|
||||||
let client = newHttpClient()
|
let client = newHttpClient()
|
||||||
|
@ -65,129 +43,4 @@ proc createDispatch*(workflowFileName: string, `ref`: string) =
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type
|
|
||||||
GhArtifact = object
|
|
||||||
id: int
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
archive_download_url*: string
|
|
||||||
|
|
||||||
GhWorkflowRun = object
|
|
||||||
id*: int
|
|
||||||
node_id: string
|
|
||||||
run_number: int
|
|
||||||
event: string
|
|
||||||
status: string
|
|
||||||
conclusion: string
|
|
||||||
html_url: string
|
|
||||||
workflow_id: int
|
|
||||||
created_at*: string # use datetime?
|
|
||||||
updated_at: string # use datetime?
|
|
||||||
|
|
||||||
ListGhArtifactResponse = object
|
|
||||||
total_count: int
|
|
||||||
artifacts: seq[GhArtifact]
|
|
||||||
|
|
||||||
ListGhWorkflowResponse = object
|
|
||||||
total_count: int
|
|
||||||
workflow_runs: seq[GhWorkflowRun]
|
|
||||||
|
|
||||||
|
|
||||||
proc listUpdateRuns(): seq[GhWorkflowRun] =
|
|
||||||
## get update.yml runs
|
|
||||||
## endpoint https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/runs
|
|
||||||
debug "listing update workflows"
|
|
||||||
let response = getGhApi("https://api.github.com/repos/daylinmorgan/oizys/actions/workflows/update.yml/runs")
|
|
||||||
fromJson(response.body, ListGhWorkflowResponse).workflow_runs
|
|
||||||
|
|
||||||
proc getLastUpdateRun*(): GhWorkflowRun =
|
|
||||||
let runs = listUpdateRuns()
|
|
||||||
let run = runs[0]
|
|
||||||
if run.conclusion == "failure":
|
|
||||||
fatalQuit bb(fmt("Most recent run was not successful\n[b]runID[/]: {run.id}\n[b]conclusion[/]: {run.conclusion}"))
|
|
||||||
if run.status in ["in_progress", "queued"]:
|
|
||||||
fatalQuit bb(fmt("Most recent run is not finished\nview workflow run at: {run.html_url}"))
|
|
||||||
result = run
|
|
||||||
|
|
||||||
|
|
||||||
proc getArtifacts(runId: int): seq[GhArtifact] =
|
|
||||||
## get workflow artifacts
|
|
||||||
## https://api.github.com/repos/OWNER/REPO/actions/runs/RUN_ID/artifacts
|
|
||||||
let response = getGhApi(fmt"https://api.github.com/repos/daylinmorgan/oizys/actions/runs/{runId}/artifacts")
|
|
||||||
fromJson(response.body, ListGhArtifactResponse).artifacts
|
|
||||||
|
|
||||||
proc getUpdateSummaryArtifact(runId: int, host: string): GhArtifact =
|
|
||||||
let name = fmt"{host}-summary"
|
|
||||||
let artifacts = getArtifacts(runId)
|
|
||||||
for artifact in artifacts:
|
|
||||||
if artifact.name == name:
|
|
||||||
return artifact
|
|
||||||
fatalQuit fmt"failed to find summary for run id: {runID}"
|
|
||||||
|
|
||||||
proc getUpdateSummaryUrl(runID: int, host: string): string =
|
|
||||||
## https://api.github.com/repos/OWNER/REPO/actions/artifacts/ARTIFACT_ID/ARCHIVE_FORMAT
|
|
||||||
let artifact = getUpdateSummaryArtifact(runID, host)
|
|
||||||
# httpclient was forwarding the Authorization headers,
|
|
||||||
# which confused Azure where the archive lives...
|
|
||||||
var response: Response
|
|
||||||
try:
|
|
||||||
let client = ghClient(maxRedirects = 0)
|
|
||||||
response = client.get(artifact.archive_download_url)
|
|
||||||
except:
|
|
||||||
errorQuit fmt("fetching summary failed:\n\n{response.headers}\n\n{response.body}")
|
|
||||||
|
|
||||||
if "location" notin response.headers.table:
|
|
||||||
errorQuit fmt("fetching summary failed:\n\n{response.headers}\n\n{response.body}")
|
|
||||||
|
|
||||||
let location = response.headers.table.getOrDefault("location", @[])
|
|
||||||
if location.len == 0: errorQuit fmt("location header missing url?")
|
|
||||||
return location[0]
|
|
||||||
|
|
||||||
proc fetchUpdateSummaryFromUrl(url: string): string =
|
|
||||||
withTmpDir:
|
|
||||||
let client = newHttpClient()
|
|
||||||
client.downloadFile(url, tmpDir / "summary.zip")
|
|
||||||
let reader = openZipArchive(tmpDir / "summary.zip")
|
|
||||||
try:
|
|
||||||
result = reader.extractFile("summary.md")
|
|
||||||
finally:
|
|
||||||
reader.close()
|
|
||||||
|
|
||||||
proc getUpdateSummary*(runId: int, host: string): string =
|
|
||||||
let url = getUpdateSummaryUrl(runId, host)
|
|
||||||
result = fetchUpdateSummaryFromUrl(url)
|
|
||||||
|
|
||||||
type
|
|
||||||
GitRepo = object
|
|
||||||
path: string
|
|
||||||
|
|
||||||
proc git(r: GitRepo, rest: varargs[string]): string =
|
|
||||||
result = "git"
|
|
||||||
result.addArgs ["-C", r.path]
|
|
||||||
result.addArgs rest
|
|
||||||
|
|
||||||
proc checkGit(code: int) =
|
|
||||||
if code != 0: fatalQuit "git had a non-zero exit status"
|
|
||||||
|
|
||||||
proc fetch(r: GitRepo) =
|
|
||||||
let code = runCmd r.git("fetch", "origin")
|
|
||||||
checkGit code
|
|
||||||
|
|
||||||
proc status(r: GitRepo) =
|
|
||||||
let (output, _, code) = runCmdCapt(r.git("status", "--porcelain"))
|
|
||||||
checkGit code
|
|
||||||
if output.len > 0:
|
|
||||||
info "unstaged commits, cowardly exiting..."
|
|
||||||
quit QuitFailure
|
|
||||||
|
|
||||||
proc rebase(r: GitRepo, `ref`: string) =
|
|
||||||
r.status()
|
|
||||||
let code = runCmd r.git("rebase", `ref`)
|
|
||||||
checkGit code
|
|
||||||
|
|
||||||
proc updateRepo*() =
|
|
||||||
let repo = GitRepo(path: getFlake())
|
|
||||||
fetch repo
|
|
||||||
rebase repo, "origin/flake-lock"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,4 +20,3 @@ gitea
|
||||||
lock
|
lock
|
||||||
code
|
code
|
||||||
comma-with-db
|
comma-with-db
|
||||||
nix-index-with-db
|
|
||||||
|
|
|
@ -94,7 +94,6 @@ proc trunc(s: string, limit: int): string =
|
||||||
|
|
||||||
proc display(msg: string, drvs: seq[Derivation]) =
|
proc display(msg: string, drvs: seq[Derivation]) =
|
||||||
echo fmt"{msg}: [bold cyan]{drvs.len()}[/]".bb
|
echo fmt"{msg}: [bold cyan]{drvs.len()}[/]".bb
|
||||||
if drvs.len > 0:
|
|
||||||
let maxLen = min(max drvs.mapIt(it.name.len), 40)
|
let maxLen = min(max drvs.mapIt(it.name.len), 40)
|
||||||
for drv in drvs:
|
for drv in drvs:
|
||||||
echo " ", drv.name.trunc(maxLen).alignLeft(maxLen), " ", drv.hash.bb("faint")
|
echo " ", drv.name.trunc(maxLen).alignLeft(maxLen), " ", drv.hash.bb("faint")
|
||||||
|
@ -131,7 +130,7 @@ proc evaluateDerivations(drvs: seq[string]): Table[string, NixDerivation] =
|
||||||
fromJson(output, Table[string,NixDerivation])
|
fromJson(output, Table[string,NixDerivation])
|
||||||
|
|
||||||
|
|
||||||
# TODO: replace asserts in this proc, would be easier with results type
|
# TODO: replace asserts in this proc
|
||||||
proc findSystemPaths(drvs: Table[string, NixDerivation]): seq[string] =
|
proc findSystemPaths(drvs: Table[string, NixDerivation]): seq[string] =
|
||||||
let hosts = getHosts()
|
let hosts = getHosts()
|
||||||
let systemDrvs = collect(
|
let systemDrvs = collect(
|
||||||
|
@ -149,8 +148,7 @@ proc findSystemPaths(drvs: Table[string, NixDerivation]): seq[string] =
|
||||||
|
|
||||||
func isIgnored(drv: string): bool =
|
func isIgnored(drv: string): bool =
|
||||||
const ignoredPackages = (slurp "ignored.txt").splitLines()
|
const ignoredPackages = (slurp "ignored.txt").splitLines()
|
||||||
let name = drv.split("-", 1)[1].replace(".drv","")
|
drv.split("-", 1)[1].replace(".drv","") in ignoredPackages
|
||||||
name in ignoredPackages
|
|
||||||
|
|
||||||
proc systemPathDrvsToBuild(): seq[string] =
|
proc systemPathDrvsToBuild(): seq[string] =
|
||||||
let toBuild = toBuildNixosConfiguration()
|
let toBuild = toBuildNixosConfiguration()
|
||||||
|
@ -159,12 +157,11 @@ proc systemPathDrvsToBuild(): seq[string] =
|
||||||
var inputDrvs: seq[string]
|
var inputDrvs: seq[string]
|
||||||
for p in systemPaths:
|
for p in systemPaths:
|
||||||
inputDrvs &= drvs[p].inputDrvs.keys().toSeq()
|
inputDrvs &= drvs[p].inputDrvs.keys().toSeq()
|
||||||
result = inputDrvs.filterIt(it in toBuild)
|
result = collect(
|
||||||
let nToBuild = result.len
|
for drv in inputDrvs:
|
||||||
result = result.filterIt(not it.isIgnored)
|
if (drv in toBuild) and (not drv.isIgnored()):
|
||||||
let nIgnored = result.len - nToBuild
|
drv & "^*"
|
||||||
debug fmt"ignored {nIgnored} derivations"
|
)
|
||||||
result = result.mapIt(it & "^*")
|
|
||||||
|
|
||||||
func splitDrv(drv: string): tuple[name, hash:string] =
|
func splitDrv(drv: string): tuple[name, hash:string] =
|
||||||
let s = drv.split("-", 1)
|
let s = drv.split("-", 1)
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# oizys-nim todo's
|
# oizys-nim todo's
|
||||||
|
|
||||||
|
- [x] nix commands including dry runs
|
||||||
|
- [ ] gh api commands
|
||||||
|
- [x] ci <- start with the easier one
|
||||||
|
- [ ] update
|
||||||
|
|
||||||
<!-- generated with <3 by daylinmorgan/todo -->
|
<!-- generated with <3 by daylinmorgan/todo -->
|
||||||
|
|
Loading…
Reference in a new issue