diff options
Diffstat (limited to 'ops')
185 files changed, 8844 insertions, 2372 deletions
diff --git a/ops/besadii/default.nix b/ops/besadii/default.nix index 48856fce06..1199c56cfb 100644 --- a/ops/besadii/default.nix +++ b/ops/besadii/default.nix @@ -1,10 +1,8 @@ # This program is used as a Gerrit hook to trigger builds on # Buildkite, Sourcegraph reindexing and other maintenance tasks. -{ ciBuilds, depot, ... }: +{ depot, ... }: -let - inherit (builtins) toFile toJSON; -in depot.nix.buildTypedGo.program { +depot.nix.buildGo.program { name = "besadii"; - srcs = [ ./main.go2 ]; + srcs = [ ./main.go ]; } diff --git a/ops/besadii/main.go b/ops/besadii/main.go new file mode 100644 index 0000000000..809acc29e8 --- /dev/null +++ b/ops/besadii/main.go @@ -0,0 +1,558 @@ +// Copyright 2019-2020 Google LLC. +// SPDX-License-Identifier: Apache-2.0 +// +// besadii is a small CLI tool that is invoked as a hook by various +// programs to cause CI-related actions. +// +// It supports the following modes & operations: +// +// Gerrit (ref-updated) hook: +// - Trigger Buildkite CI builds +// - Trigger SourceGraph repository index updates +// +// Buildkite (post-command) hook: +// - Submit CL verification status back to Gerrit +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "log/syslog" + "net/http" + "net/mail" + "os" + "os/user" + "path" + "regexp" + "strconv" + "strings" +) + +// Regular expression to extract change ID out of a URL +var changeIdRegexp = regexp.MustCompile(`^.*/(\d+)$`) + +// Regular expression to check if gerritChangeName valid. The +// limitation could be what is allowed for a git branch name. For now +// we want to have a stricter limitation for readability and ease of +// use. +var gerritChangeNameRegexp = `^[a-z0-9]+$` +var gerritChangeNameCheck = regexp.MustCompile(gerritChangeNameRegexp) + +// besadii configuration file structure +type config struct { + // Required configuration for Buildkite<>Gerrit monorepo + // integration. + Repository string `json:"repository"` + Branch string `json:"branch"` + GerritUrl string `json:"gerritUrl"` + GerritUser string `json:"gerritUser"` + GerritPassword string `json:"gerritPassword"` + GerritLabel string `json:"gerritLabel"` + BuildkiteOrg string `json:"buildkiteOrg"` + BuildkiteProject string `json:"buildkiteProject"` + BuildkiteToken string `json:"buildkiteToken"` + GerritChangeName string `json:"gerritChangeName"` + + // Optional configuration for Sourcegraph trigger updates. + SourcegraphUrl string `json:"sourcegraphUrl"` + SourcegraphToken string `json:"sourcegraphToken"` +} + +// buildTrigger represents the information passed to besadii when it +// is invoked as a Gerrit hook. +// +// https://gerrit.googlesource.com/plugins/hooks/+/HEAD/src/main/resources/Documentation/hooks.md +type buildTrigger struct { + project string + ref string + commit string + author string + email string + + changeId string + patchset string +} + +type Author struct { + Name string `json:"name"` + Email string `json:"email"` +} + +// Build is the representation of a Buildkite build as described on +// https://buildkite.com/docs/apis/rest-api/builds#create-a-build +type Build struct { + Commit string `json:"commit"` + Branch string `json:"branch"` + Author Author `json:"author"` + Env map[string]string `json:"env"` +} + +// BuildResponse is the representation of Buildkite's success response +// after triggering a build. This has many fields, but we only need +// one of them. +type buildResponse struct { + WebUrl string `json:"web_url"` +} + +// reviewInput is a struct representing the data submitted to Gerrit +// to post a review on a CL. +// +// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#review-input +type reviewInput struct { + Message string `json:"message"` + Labels map[string]int `json:"labels,omitempty"` + OmitDuplicateComments bool `json:"omit_duplicate_comments"` + IgnoreDefaultAttentionSetRules bool `json:"ignore_default_attention_set_rules"` + Tag string `json:"tag"` + Notify string `json:"notify,omitempty"` +} + +func defaultConfigLocation() (string, error) { + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to get current user: %w", err) + } + + return path.Join(usr.HomeDir, "besadii.json"), nil +} + +func loadConfig() (*config, error) { + configPath := os.Getenv("BESADII_CONFIG") + + if configPath == "" { + var err error + configPath, err = defaultConfigLocation() + if err != nil { + return nil, fmt.Errorf("failed to get config location: %w", err) + } + } + + configJson, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to load besadii config: %w", err) + } + + var cfg config + err = json.Unmarshal(configJson, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal besadii config: %w", err) + } + + // The default Gerrit label to set is 'Verified', unless specified otherwise. + if cfg.GerritLabel == "" { + cfg.GerritLabel = "Verified" + } + + // The default text referring to a Gerrit Change in BuildKite. + if cfg.GerritChangeName == "" { + cfg.GerritChangeName = "cl" + } + if !gerritChangeNameCheck.MatchString(cfg.GerritChangeName) { + return nil, fmt.Errorf("invalid 'gerritChangeName': %s", cfg.GerritChangeName) + } + + // Rudimentary config validation logic + if cfg.SourcegraphUrl != "" && cfg.SourcegraphToken == "" { + return nil, fmt.Errorf("'SourcegraphToken' must be set if 'SourcegraphUrl' is set") + } + + if cfg.Repository == "" || cfg.Branch == "" { + return nil, fmt.Errorf("missing repository configuration (required: repository, branch)") + } + + if cfg.GerritUrl == "" || cfg.GerritUser == "" || cfg.GerritPassword == "" { + return nil, fmt.Errorf("missing Gerrit configuration (required: gerritUrl, gerritUser, gerritPassword)") + } + + if cfg.BuildkiteOrg == "" || cfg.BuildkiteProject == "" || cfg.BuildkiteToken == "" { + return nil, fmt.Errorf("mising Buildkite configuration (required: buildkiteOrg, buildkiteProject, buildkiteToken)") + } + + return &cfg, nil +} + +// linkToChange creates the full link to a change's patchset in Gerrit +func linkToChange(cfg *config, changeId, patchset string) string { + return fmt.Sprintf("%s/c/%s/+/%s/%s", cfg.GerritUrl, cfg.Repository, changeId, patchset) +} + +// updateGerrit posts a comment on a Gerrit CL to indicate the current build status. +func updateGerrit(cfg *config, review reviewInput, changeId, patchset string) { + body, _ := json.Marshal(review) + reader := io.NopCloser(bytes.NewReader(body)) + + url := fmt.Sprintf("%s/a/changes/%s/revisions/%s/review", cfg.GerritUrl, changeId, patchset) + req, err := http.NewRequest("POST", url, reader) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %s", err) + os.Exit(1) + } + + req.SetBasicAuth(cfg.GerritUser, cfg.GerritPassword) + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to update %s on %s: %s", cfg.GerritChangeName, cfg.GerritUrl, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + respBody, _ := io.ReadAll(resp.Body) + fmt.Fprintf(os.Stderr, "received non-success response from Gerrit: %s (%v)", respBody, resp.Status) + } else { + fmt.Printf("Added CI status comment on %s", linkToChange(cfg, changeId, patchset)) + } +} + +// Trigger a build of a given branch & commit on Buildkite +func triggerBuild(cfg *config, log *syslog.Writer, trigger *buildTrigger) error { + env := make(map[string]string) + branch := trigger.ref + + // Pass information about the originating Gerrit change to the + // build, if it is for a patchset. + // + // This information is later used by besadii when invoked by Gerrit + // to communicate the build status back to Gerrit. + headBuild := true + if trigger.changeId != "" && trigger.patchset != "" { + env["GERRIT_CHANGE_URL"] = linkToChange(cfg, trigger.changeId, trigger.patchset) + env["GERRIT_CHANGE_ID"] = trigger.changeId + env["GERRIT_PATCHSET"] = trigger.patchset + headBuild = false + + // The branch doesn't have to be a real ref (it's just used to + // group builds), so make it the identifier for the CL. + branch = fmt.Sprintf("%s/%v", cfg.GerritChangeName, strings.Split(trigger.ref, "/")[3]) + } + + build := Build{ + Commit: trigger.commit, + Branch: branch, + Env: env, + Author: Author{ + Name: trigger.author, + Email: trigger.email, + }, + } + + body, _ := json.Marshal(build) + reader := io.NopCloser(bytes.NewReader(body)) + + bkUrl := fmt.Sprintf("https://api.buildkite.com/v2/organizations/%s/pipelines/%s/builds", cfg.BuildkiteOrg, cfg.BuildkiteProject) + req, err := http.NewRequest("POST", bkUrl, reader) + if err != nil { + return fmt.Errorf("failed to create an HTTP request: %w", err) + } + + req.Header.Add("Authorization", "Bearer "+cfg.BuildkiteToken) + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + // This might indicate a temporary error on the Buildkite side. + return fmt.Errorf("failed to send Buildkite request: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read Buildkite response body: %w", err) + } + + if resp.StatusCode != http.StatusCreated { + return fmt.Errorf("received non-success response from Buildkite: %s (%v)", respBody, resp.Status) + } + + var buildResp buildResponse + err = json.Unmarshal(respBody, &buildResp) + if err != nil { + return fmt.Errorf("failed to unmarshal build response: %w", err) + } + + fmt.Fprintf(log, "triggered build for ref %q at commit %q: %s", trigger.ref, trigger.commit, buildResp.WebUrl) + + // For builds of the HEAD branch there is nothing else to do + if headBuild { + return nil + } + + // Report the status back to the Gerrit CL so that users can click + // through to the running build. + msg := fmt.Sprintf("Started build for patchset #%s on: %s", trigger.patchset, buildResp.WebUrl) + review := reviewInput{ + Message: msg, + OmitDuplicateComments: true, + Tag: "autogenerated:buildkite~trigger", + + // Do not update the attention set for this comment. + IgnoreDefaultAttentionSetRules: true, + + Notify: "NONE", + } + updateGerrit(cfg, review, trigger.changeId, trigger.patchset) + + return nil +} + +// Trigger a Sourcegraph repository index update. +// +// https://docs.sourcegraph.com/admin/repo/webhooks +func triggerIndexUpdate(cfg *config, log *syslog.Writer) error { + req, err := http.NewRequest("POST", cfg.SourcegraphUrl, nil) + if err != nil { + return err + } + + req.Header.Add("Authorization", "token "+cfg.SourcegraphToken) + + _, err = http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to trigger Sourcegraph index update: %w", err) + } + + log.Info("triggered sourcegraph index update") + return nil +} + +// Gerrit passes more flags than we want, but Rob Pike decided[0] in +// 2013 that the Go art project will not allow users to ignore flags +// because he "doesn't like it". This function allows users to ignore +// flags. +// +// [0]: https://github.com/golang/go/issues/6112#issuecomment-66083768 +func ignoreFlags(ignore []string) { + for _, f := range ignore { + flag.String(f, "", "flag to ignore") + } +} + +// Extract the username & email from Gerrit's uploader flag and set it +// on the trigger struct, for display in Buildkite. +func extractChangeUploader(uploader string, trigger *buildTrigger) error { + // Gerrit passes the uploader in another extra layer of quotes. + uploader, err := strconv.Unquote(uploader) + if err != nil { + return fmt.Errorf("failed to unquote email - forgot quotes on manual invocation?: %w", err) + } + + // Extract the uploader username & email from the input passed by + // Gerrit (in RFC 5322 format). + addr, err := mail.ParseAddress(uploader) + if err != nil { + return fmt.Errorf("invalid change uploader (%s): %w", uploader, err) + } + + trigger.author = addr.Name + trigger.email = addr.Address + + return nil +} + +// Extract the buildtrigger struct out of the flags passed to besadii +// when invoked as Gerrit's 'patchset-created' hook. This hook is used +// for triggering CI on in-progress CLs. +func buildTriggerFromPatchsetCreated(cfg *config) (*buildTrigger, error) { + // Information that needs to be returned + var trigger buildTrigger + + // Information that is only needed for parsing + var targetBranch, changeUrl, uploader, kind string + + flag.StringVar(&trigger.project, "project", "", "Gerrit project") + flag.StringVar(&trigger.commit, "commit", "", "commit hash") + flag.StringVar(&trigger.patchset, "patchset", "", "patchset ID") + + flag.StringVar(&targetBranch, "branch", "", "CL target branch") + flag.StringVar(&changeUrl, "change-url", "", "HTTPS URL of change") + flag.StringVar(&uploader, "uploader", "", "Change uploader name & email") + flag.StringVar(&kind, "kind", "", "Kind of patchset") + + // patchset-created also passes various flags which we don't need. + ignoreFlags([]string{"topic", "change", "uploader-username", "change-owner", "change-owner-username"}) + + flag.Parse() + + // Ignore patchsets which do not contain code changes + if kind == "NO_CODE_CHANGE" || kind == "NO_CHANGE" { + return nil, nil + } + + // Parse username & email + err := extractChangeUploader(uploader, &trigger) + if err != nil { + return nil, err + } + + // If the patchset is not for the HEAD branch of the monorepo, then + // we can ignore it. It might be some other kind of change + // (refs/meta/config or Gerrit-internal), but it is not an error. + if trigger.project != cfg.Repository || targetBranch != cfg.Branch { + return nil, nil + } + + // Change ID is not directly passed in the numeric format, so we + // need to extract it out of the URL + matches := changeIdRegexp.FindStringSubmatch(changeUrl) + trigger.changeId = matches[1] + + // Construct the CL ref from which the build should happen. + changeId, _ := strconv.Atoi(trigger.changeId) + trigger.ref = fmt.Sprintf( + "refs/changes/%02d/%s/%s", + changeId%100, trigger.changeId, trigger.patchset, + ) + + return &trigger, nil +} + +// Extract the buildtrigger struct out of the flags passed to besadii +// when invoked as Gerrit's 'change-merged' hook. This hook is used +// for triggering HEAD builds after change submission. +func buildTriggerFromChangeMerged(cfg *config) (*buildTrigger, error) { + // Information that needs to be returned + var trigger buildTrigger + + // Information that is only needed for parsing + var targetBranch, submitter string + + flag.StringVar(&trigger.project, "project", "", "Gerrit project") + flag.StringVar(&trigger.commit, "commit", "", "Commit hash") + flag.StringVar(&submitter, "submitter", "", "Submitter email & username") + flag.StringVar(&targetBranch, "branch", "", "CL target branch") + + // Ignore extra flags passed by change-merged + ignoreFlags([]string{"change", "topic", "change-url", "submitter-username", "newrev", "change-owner", "change-owner-username"}) + + flag.Parse() + + // Parse username & email + err := extractChangeUploader(submitter, &trigger) + if err != nil { + return nil, err + } + + // If the patchset is not for the HEAD branch of the monorepo, then + // we can ignore it. + if trigger.project != cfg.Repository || targetBranch != cfg.Branch { + return nil, nil + } + + trigger.ref = "refs/heads/" + targetBranch + + return &trigger, nil +} + +func gerritHookMain(cfg *config, log *syslog.Writer, trigger *buildTrigger) { + if trigger == nil { + // The hook was not for something we care about. + os.Exit(0) + } + + err := triggerBuild(cfg, log, trigger) + + if err != nil { + log.Err(fmt.Sprintf("failed to trigger Buildkite build: %s", err)) + } + + if cfg.SourcegraphUrl != "" && trigger.ref == cfg.Branch { + err = triggerIndexUpdate(cfg, log) + if err != nil { + log.Err(fmt.Sprintf("failed to trigger sourcegraph index update: %s", err)) + } + } +} + +func postCommandMain(cfg *config) { + changeId := os.Getenv("GERRIT_CHANGE_ID") + patchset := os.Getenv("GERRIT_PATCHSET") + + if changeId == "" || patchset == "" { + // If these variables are unset, but the hook was invoked, the + // build was most likely for a branch and not for a CL - no status + // needs to be reported back to Gerrit! + fmt.Printf("This isn't a %s build, nothing to do. Have a nice day!\n", cfg.GerritChangeName) + return + } + + if os.Getenv("BUILDKITE_LABEL") != ":duck:" { + // this is not the build stage, don't do anything. + return + } + + var vote int + var verb string + var notify string + + if os.Getenv("BUILDKITE_COMMAND_EXIT_STATUS") == "0" { + vote = 1 // automation passed: +1 in Gerrit + verb = "passed" + notify = "NONE" + } else { + vote = -1 + verb = "failed" + notify = "OWNER" + } + + msg := fmt.Sprintf("Build of patchset %s %s: %s", patchset, verb, os.Getenv("BUILDKITE_BUILD_URL")) + review := reviewInput{ + Message: msg, + OmitDuplicateComments: true, + Labels: map[string]int{ + cfg.GerritLabel: vote, + }, + + // Update the attention set if we are failing this patchset. + IgnoreDefaultAttentionSetRules: vote == 1, + + Tag: "autogenerated:buildkite~result", + + Notify: notify, + } + updateGerrit(cfg, review, changeId, patchset) +} + +func main() { + // Logging happens in syslog because it's almost impossible to get + // output out of Gerrit hooks otherwise. + log, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "besadii") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open syslog: %s\n", err) + os.Exit(1) + } + + log.Info(fmt.Sprintf("besadii called with arguments: %v", os.Args)) + + bin := path.Base(os.Args[0]) + cfg, err := loadConfig() + + if err != nil { + log.Crit(fmt.Sprintf("besadii configuration error: %v", err)) + os.Exit(4) + } + + if bin == "patchset-created" { + trigger, err := buildTriggerFromPatchsetCreated(cfg) + if err != nil { + log.Crit(fmt.Sprintf("failed to parse 'patchset-created' invocation from args: %v", err)) + os.Exit(1) + } + gerritHookMain(cfg, log, trigger) + } else if bin == "change-merged" { + trigger, err := buildTriggerFromChangeMerged(cfg) + if err != nil { + log.Crit(fmt.Sprintf("failed to parse 'change-merged' invocation from args: %v", err)) + os.Exit(1) + } + gerritHookMain(cfg, log, trigger) + } else if bin == "post-command" { + postCommandMain(cfg) + } else { + fmt.Fprintf(os.Stderr, "besadii does not know how to be invoked as %q, sorry!", bin) + os.Exit(1) + } +} diff --git a/ops/besadii/main.go2 b/ops/besadii/main.go2 deleted file mode 100644 index 998c677010..0000000000 --- a/ops/besadii/main.go2 +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright 2019-2020 Google LLC. -// SPDX-License-Identifier: Apache-2.0 -// -// besadii is a small CLI tool that is invoked as a hook by various -// programs to cause CI-related actions. -// -// It supports the following modes & operations: -// -// Gerrit (ref-updated) hook: -// - Trigger Buildkite CI builds -// - Trigger SourceGraph (cs.tvl.fyi) repository index updates -// -// Buildkite (post-command) hook: -// - Submit CL verification status back to Gerrit -package main - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log/syslog" - "net/http" - "os" - "path" - "regexp" -) - -var branchRegexp = regexp.MustCompile(`^refs/heads/(.*)$`) -var metaRegexp = regexp.MustCompile(`^refs/changes/\d{0,2}/(\d+)/meta$`) -var patchsetRegexp = regexp.MustCompile(`^refs/changes/\d{0,2}/(\d+)/(\d+)$`) - -// refUpdated is a struct representing the information passed to -// besadii when it is invoked as Gerrit's refUpdated hook. -// -// https://gerrit.googlesource.com/plugins/hooks/+/HEAD/src/main/resources/Documentation/hooks.md#ref_updated -type refUpdated struct { - project string - ref string - commit string - submitter string - email string - - changeId *string - patchset *string -} - -type Author struct { - Name string `json:"name"` - Email string `json:"email"` -} - -// Build is the representation of a Buildkite build as described on -// https://buildkite.com/docs/apis/rest-api/builds#create-a-build -type Build struct { - Commit string `json:"commit"` - Branch string `json:"branch"` - Message string `json:"message"` - Author Author `json:"author"` - Env map[string]string `json:"env"` -} - -// Trigger a build of a given branch & commit on Buildkite -func triggerBuild(log *syslog.Writer, token string, update *refUpdated) error { - var message string - env := make(map[string]string) - - if update.changeId != nil && update.patchset != nil { - env["GERRIT_CHANGE_ID"] = *update.changeId - env["GERRIT_PATCHSET"] = *update.patchset - message = fmt.Sprintf(":llama: depot @ https://cl.tvl.fyi/c/depot/+/%s/%s", *update.changeId, *update.patchset) - } else { - message = fmt.Sprintf(":llama: depot @ %s", update.commit) - } - - build := Build{ - Commit: update.commit, - Branch: update.ref, - Message: message, - Env: env, - Author: Author{ - Name: update.submitter, - Email: update.email, - }, - } - - body, _ := json.Marshal(build) - reader := ioutil.NopCloser(bytes.NewReader(body)) - - req, err := http.NewRequest("POST", "https://api.buildkite.com/v2/organizations/tvl/pipelines/depot/builds", reader) - if err != nil { - return fmt.Errorf("failed to create an HTTP request: %w", err) - } - - req.Header.Add("Authorization", "Bearer "+token) - req.Header.Add("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - // This might indicate a temporary error on the Buildkite side. - return fmt.Errorf("failed to send Buildkite request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 201 { - respBody, _ := ioutil.ReadAll(resp.Body) - log.Err(fmt.Sprintf("received non-success response from Buildkite: %s (%v)", respBody, resp.Status)) - } else { - fmt.Fprintf(log, "triggered Buildkite build for ref %q at commit %q", update.ref, update.commit) - } - - return nil -} - -// Trigger a Sourcegraph repository index update on cs.tvl.fyi. -// -// https://docs.sourcegraph.com/admin/repo/webhooks -func triggerIndexUpdate(token string) error { - req, err := http.NewRequest("POST", "https://cs.tvl.fyi/.api/repos/depot/-/refresh", nil) - if err != nil { - return err - } - - req.Header.Add("Authorization", "token "+token) - - _, err = http.DefaultClient.Do(req) - return err -} - -func refUpdatedFromFlags() (*refUpdated, error) { - var update refUpdated - - flag.StringVar(&update.project, "project", "", "Gerrit project") - flag.StringVar(&update.commit, "newrev", "", "new revision") - flag.StringVar(&update.email, "submitter", "", "Submitter email") - flag.StringVar(&update.submitter, "submitter-username", "", "Submitter username") - flag.StringVar(&update.ref, "refname", "", "updated reference name") - - // Gerrit passes more flags than we want, but Rob Pike decided[0] in - // 2013 that the Go art project will not allow users to ignore flags - // because he "doesn't like it". The following code ignores the - // flags. - // - // [0]: https://github.com/golang/go/issues/6112#issuecomment-66083768 - var _old string - flag.StringVar(&_old, "oldrev", "", "") - - flag.Parse() - - if update.project == "" || update.ref == "" || update.commit == "" || update.submitter == "" { - // If we get here, the user is probably being a dummy and invoking - // this manually - but incorrectly. - return nil, fmt.Errorf("'ref-updated' hook invoked without required arguments") - } - - if update.project != "depot" || metaRegexp.MatchString(update.ref) { - // this is not an error, but also not something we handle. - return nil, nil - } - - if branchRegexp.MatchString(update.ref) { - // these refs don't need special handling, just move on - return &update, nil - } - - if matches := patchsetRegexp.FindStringSubmatch(update.ref); matches != nil { - update.changeId = &matches[1] - update.patchset = &matches[2] - return &update, nil - } - - return nil, fmt.Errorf("besadii does not support updates for this type of ref (%q)", update.ref) -} - -func refUpdatedMain() { - // Logging happens in syslog for Gerrit hooks because we don't want - // the hook output to be intermingled with Gerrit's own output - // stream - log, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "besadii") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open syslog: %s\n", err) - os.Exit(1) - } - - update, err := refUpdatedFromFlags() - if err != nil { - log.Err(fmt.Sprintf("failed to parse ref update: %s", err)) - os.Exit(1) - } - - if update == nil { // the project was not 'depot' - log.Err("build triggers are only supported for the 'depot' project") - os.Exit(0) - } - - buildkiteToken, err := ioutil.ReadFile("/etc/secrets/buildkite-besadii") - if err != nil { - log.Alert(fmt.Sprintf("buildkite token could not be read: %s", err)) - os.Exit(1) - } - - sourcegraphToken, err := ioutil.ReadFile("/etc/secrets/sourcegraph-token") - if err != nil { - log.Alert(fmt.Sprintf("sourcegraph token could not be read: %s", err)) - os.Exit(1) - } - - err = triggerBuild(log, string(buildkiteToken), update) - if err != nil { - log.Err(fmt.Sprintf("failed to trigger Buildkite build: %s", err)) - } - - err = triggerIndexUpdate(string(sourcegraphToken)) - if err != nil { - log.Err(fmt.Sprintf("failed to trigger sourcegraph index update: %s", err)) - } - log.Info("triggered sourcegraph index update") -} - -// reviewInput is a struct representing the data submitted to Gerrit -// to post a review on a CL. -// -// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#review-input -type reviewInput struct { - Message string `json:"message"` - Labels map[string]int `json:"labels"` - OmitDuplicateComments bool `json:"omit_duplicate_comments"` - IgnoreDefaultAttentionSetRules bool `json:"ignore_default_attention_set_rules"` -} - -func postCommandMain() { - changeId := os.Getenv("GERRIT_CHANGE_ID") - patchset := os.Getenv("GERRIT_PATCHSET") - - if changeId == "" || patchset == "" { - // If these variables are unset, but the hook was invoked, the - // build was most likely for a branch and not for a CL - no status - // needs to be reported back to Gerrit! - fmt.Println("This isn't a CL build, nothing to do. Have a nice day!") - return - } - - if os.Getenv("BUILDKITE_LABEL") != ":duck:" { - // this is not the build stage, don't do anything. - return - } - - gerritPassword, err := ioutil.ReadFile("/etc/secrets/buildkite-gerrit") - if err != nil { - fmt.Fprintf(os.Stderr, "Gerrit password could not be read: %s", err) - os.Exit(1) - } - - var verified int - var verb string - - if os.Getenv("BUILDKITE_COMMAND_EXIT_STATUS") == "0" { - verified = 1 // Verified: +1 in Gerrit - verb = "passed" - } else { - verified = -1 - verb = "failed" - } - - msg := fmt.Sprintf("Build of patchset %s %s: %s", patchset, verb, os.Getenv("BUILDKITE_BUILD_URL")) - review := reviewInput{ - Message: msg, - OmitDuplicateComments: true, - Labels: map[string]int{ - "Verified": verified, - }, - - // Update the attention set if we are failing this patchset. - IgnoreDefaultAttentionSetRules: verified == 1, - } - - body, _ := json.Marshal(review) - reader := ioutil.NopCloser(bytes.NewReader(body)) - - url := fmt.Sprintf("https://cl.tvl.fyi/a/changes/%s/revisions/%s/review", changeId, patchset) - req, err := http.NewRequest("POST", url, reader) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %w", err) - os.Exit(1) - } - - req.SetBasicAuth("buildkite", string(gerritPassword)) - req.Header.Add("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - fmt.Errorf("failed to update CL on Gerrit: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - respBody, _ := ioutil.ReadAll(resp.Body) - fmt.Fprintf(os.Stderr, "received non-success response from Gerrit: %s (%v)", respBody, resp.Status) - } else { - fmt.Printf("Updated CI status on https://cl.tvl.fyi/c/depot/+/%s/%s", changeId, patchset) - } -} - -func main() { - bin := path.Base(os.Args[0]) - - if bin == "ref-updated" { - refUpdatedMain() - } else if bin == "post-command" { - postCommandMain() - } else { - fmt.Fprintf(os.Stderr, "besadii does not know how to be invoked as %q, sorry!", bin) - os.Exit(1) - } -} diff --git a/ops/buildkite/.gitignore b/ops/buildkite/.gitignore new file mode 100644 index 0000000000..41c1b33462 --- /dev/null +++ b/ops/buildkite/.gitignore @@ -0,0 +1,2 @@ +.envrc +.terraform* diff --git a/ops/buildkite/README.md b/ops/buildkite/README.md new file mode 100644 index 0000000000..9d31a53fd3 --- /dev/null +++ b/ops/buildkite/README.md @@ -0,0 +1,24 @@ +Buildkite configuration +======================= + +This contains Terraform configuration for setting up our Buildkite +pipelines. + +Each pipeline (such as the one for depot itself, or exported subsets +of the depot) needs some static configuration stored in Buildkite. + +Through `//tools/depot-deps` a `tf-buildkite` binary is made available +which contains a Terraform binary pre-configured with the correct +providers. This is automatically on your `$PATH` through `direnv`. + +However, secrets still need to be loaded to access the Terraform state +and speak to the Buildkite API. These are available to certain users +through `//ops/secrets`. + +This can be done with separate direnv configuration, for example: + +``` +# //ops/buildkite/.envrc +source_up +eval $(age --decrypt -i ~/.ssh/id_ed25519 $(git rev-parse --show-toplevel)/ops/secrets/tf-buildkite.age) +``` diff --git a/ops/buildkite/default.nix b/ops/buildkite/default.nix new file mode 100644 index 0000000000..0d39bc0601 --- /dev/null +++ b/ops/buildkite/default.nix @@ -0,0 +1,14 @@ +{ depot, lib, pkgs, ... }: + +depot.nix.readTree.drvTargets rec { + terraform = pkgs.terraform.withPlugins (p: [ + p.buildkite + ]); + + validate = depot.tools.checks.validateTerraform { + inherit terraform; + name = "buildkite"; + src = lib.cleanSource ./.; + env.BUILDKITE_API_TOKEN = "ci-dummy"; + }; +} diff --git a/ops/buildkite/steps-depot.yml b/ops/buildkite/steps-depot.yml new file mode 100644 index 0000000000..011b299771 --- /dev/null +++ b/ops/buildkite/steps-depot.yml @@ -0,0 +1,6 @@ +--- +steps: + - label: ":buildkite:" + key: ":init:" + command: | + buildkite-agent pipeline upload ops/pipelines/static-pipeline.yaml diff --git a/ops/buildkite/steps-tvix.yml b/ops/buildkite/steps-tvix.yml new file mode 100644 index 0000000000..a6e9f13b16 --- /dev/null +++ b/ops/buildkite/steps-tvix.yml @@ -0,0 +1,4 @@ +--- +steps: + - label: ":buildkite: Upload pipeline" + command: "buildkite-agent pipeline upload" diff --git a/ops/buildkite/steps-tvl-kit.yml b/ops/buildkite/steps-tvl-kit.yml new file mode 100644 index 0000000000..a6e9f13b16 --- /dev/null +++ b/ops/buildkite/steps-tvl-kit.yml @@ -0,0 +1,4 @@ +--- +steps: + - label: ":buildkite: Upload pipeline" + command: "buildkite-agent pipeline upload" diff --git a/ops/buildkite/tvl.tf b/ops/buildkite/tvl.tf new file mode 100644 index 0000000000..4c45909a0c --- /dev/null +++ b/ops/buildkite/tvl.tf @@ -0,0 +1,48 @@ +# Buildkite configuration for TVL. + +terraform { + required_providers { + buildkite = { + source = "buildkite/buildkite" + } + } + + backend "s3" { + endpoint = "https://objects.dc-sto1.glesys.net" + bucket = "tvl-state" + key = "terraform/tvl-buildkite" + region = "glesys" + + skip_credentials_validation = true + skip_region_validation = true + skip_metadata_api_check = true + } +} + +provider "buildkite" { + organization = "tvl" +} + +resource "buildkite_pipeline" "depot" { + name = "depot" + description = "Run full CI pipeline of the depot, TVL's monorepo." + repository = "https://cl.tvl.fyi/depot" + steps = file("./steps-depot.yml") + default_branch = "refs/heads/canon" +} + +resource "buildkite_pipeline" "tvix" { + name = "tvix" + description = "Tvix, an exported subset of TVL depot" + repository = "https://code.tvl.fyi/depot.git:workspace=views/tvix.git" + steps = file("./steps-tvix.yml") + default_branch = "canon" +} + +resource "buildkite_pipeline" "tvl_kit" { + name = "tvl-kit" + description = "TVL Kit, an exported subset of TVL depot" + repository = "https://code.tvl.fyi/depot.git:workspace=views/kit.git" + steps = file("./steps-tvl-kit.yml") + default_branch = "canon" +} diff --git a/ops/deploy-whitby/default.nix b/ops/deploy-whitby/default.nix new file mode 100644 index 0000000000..aafe798cbf --- /dev/null +++ b/ops/deploy-whitby/default.nix @@ -0,0 +1,31 @@ +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "deploy-whitby"; + + phases = [ "installPhase" "installCheckPhase" ]; + + nativeBuildInputs = with pkgs; [ + makeWrapper + ]; + + installPhase = '' + mkdir -p $out/bin + makeWrapper ${./deploy-whitby.sh} $out/bin/deploy-whitby.sh \ + --prefix PATH : ${with pkgs; lib.makeBinPath [ + ansi2html + git + jq + nvd + ]} + ''; + + installCheckInputs = with pkgs; [ + shellcheck + ]; + + doInstallCheck = true; + installCheckPhase = '' + shellcheck $out/bin/deploy-whitby.sh + ''; +} diff --git a/ops/deploy-whitby/deploy-whitby.sh b/ops/deploy-whitby/deploy-whitby.sh new file mode 100755 index 0000000000..756aa7ae08 --- /dev/null +++ b/ops/deploy-whitby/deploy-whitby.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -Ceuo pipefail + +HTML_ROOT="${HTML_ROOT:-/var/html/deploys.tvl.fyi}" +URL_BASE="${URL_BASE:-https://deploys.tvl.fyi/diff}" +IRCCAT_PORT="${IRCCAT_PORT:-4722}" + +drv_hash() { + basename "$1" | sed 's/-.*//' +} + +new_rev="$1" + +if [ -z "$new_rev" ]; then + >&2 echo "Usage: $0 <new_rev>" + exit 1 +fi + +if [ -d "/tmp/deploy.worktree" ]; then + >&2 echo "/tmp/deploy.worktree exists - exiting in case another deploy is currently running" + exit 1 +fi + +worktree_dir=/tmp/worktree_dir + +cleanup() { + rm -rf "$worktree_dir" +} +trap cleanup EXIT + +git clone https://cl.tvl.fyi/depot "$worktree_dir" --reference /depot +git -C "$worktree_dir" checkout "$new_rev" + +current=$(nix show-derivation /run/current-system | jq -r 'keys | .[0]') +new=$(nix-instantiate -A ops.nixos.whitbySystem "$worktree_dir") + +diff_filename="$(drv_hash "$current")..$(drv_hash "$new").html" +nvd --color always diff "$current" "$new" \ + | ansi2html \ + >| "$HTML_ROOT/diff/$diff_filename" +chmod a+r "$HTML_ROOT/diff/$diff_filename" + +echo "#tvl whitby is being deployed! system diff: $URL_BASE/$diff_filename" \ + | nc -w 5 -N localhost "$IRCCAT_PORT" + +# TODO(grfn): Actually do the deploy diff --git a/ops/dns/README.md b/ops/dns/README.md new file mode 100644 index 0000000000..2290299fe4 --- /dev/null +++ b/ops/dns/README.md @@ -0,0 +1,11 @@ +DNS configuration +================= + +This folder contains configuration for our DNS zones. The zones are hosted with +Google Cloud DNS, which supports zone-file based import/export. + +Currently there is no automation to deploy these zones, but CI will check their +integrity. + +*Note: While each zone file specifies an SOA record, it only exists to satisfy +`named-checkzone`. Cloud DNS manages this record for us.* diff --git a/ops/dns/default.nix b/ops/dns/default.nix new file mode 100644 index 0000000000..33fe6d6fe7 --- /dev/null +++ b/ops/dns/default.nix @@ -0,0 +1,14 @@ +# Performs simple (local-only) validity checks on DNS zones. +{ depot, pkgs, ... }: + +let + checkZone = zone: file: pkgs.runCommand "${zone}-check" { } '' + ${pkgs.bind}/bin/named-checkzone -i local ${zone} ${file} | tee $out + ''; + +in +depot.nix.readTree.drvTargets { + nixery-dev = checkZone "nixery.dev" ./nixery.dev.zone; + tvl-fyi = checkZone "tvl.fyi" ./tvl.fyi.zone; + tvl-su = checkZone "tvl.su" ./tvl.su.zone; +} diff --git a/ops/dns/nixery.dev.zone b/ops/dns/nixery.dev.zone new file mode 100644 index 0000000000..44cabab29b --- /dev/null +++ b/ops/dns/nixery.dev.zone @@ -0,0 +1,10 @@ +;; Google Cloud DNS zone for nixery.dev +nixery.dev. 21600 IN SOA ns-cloud-b1.googledomains.com. cloud-dns-hostmaster.google.com. 5 21600 3600 259200 300 +nixery.dev. 21600 IN NS ns-cloud-b1.googledomains.com. +nixery.dev. 21600 IN NS ns-cloud-b2.googledomains.com. +nixery.dev. 21600 IN NS ns-cloud-b3.googledomains.com. +nixery.dev. 21600 IN NS ns-cloud-b4.googledomains.com. + +;; Records for pointing nixery.dev to whitby +nixery.dev. 300 IN A 49.12.129.211 +nixery.dev. 300 IN AAAA 2a01:4f8:242:5b21:0:feed:edef:beef diff --git a/ops/dns/tvl.fyi.zone b/ops/dns/tvl.fyi.zone new file mode 100644 index 0000000000..d1961c6a7a --- /dev/null +++ b/ops/dns/tvl.fyi.zone @@ -0,0 +1,39 @@ +;; Google Cloud DNS zone for tvl.fyi. +;; +;; This zone is hosted in the project 'tvl-fyi', and registered via +;; Google Domains. +tvl.fyi. 21600 IN SOA ns-cloud-b1.googledomains.com. cloud-dns-hostmaster.google.com. 20 21600 3600 259200 300 +tvl.fyi. 21600 IN NS ns-cloud-b1.googledomains.com. +tvl.fyi. 21600 IN NS ns-cloud-b2.googledomains.com. +tvl.fyi. 21600 IN NS ns-cloud-b3.googledomains.com. +tvl.fyi. 21600 IN NS ns-cloud-b4.googledomains.com. + +;; Mail forwarding (via domains.google) +tvl.fyi. 3600 IN MX 5 gmr-smtp-in.l.google.com. +tvl.fyi. 3600 IN MX 10 alt1.gmr-smtp-in.l.google.com. +tvl.fyi. 3600 IN MX 20 alt2.gmr-smtp-in.l.google.com. +tvl.fyi. 3600 IN MX 30 alt3.gmr-smtp-in.l.google.com. +tvl.fyi. 3600 IN MX 40 alt4.gmr-smtp-in.l.google.com. + +;; Landing website is hosted on whitby on the apex. +tvl.fyi. 21600 IN A 49.12.129.211 +tvl.fyi. 21600 IN AAAA 2a01:4f8:242:5b21:0:feed:edef:beef + +;; TVL infrastructure +whitby.tvl.fyi. 21600 IN A 49.12.129.211 +whitby.tvl.fyi. 21600 IN AAAA 2a01:4f8:242:5b21:0:feed:edef:beef + +;; TVL services +at.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +atward.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +b.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +cache.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +cl.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +code.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +cs.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +deploys.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +images.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +login.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +static.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +status.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. +todo.tvl.fyi. 21600 IN CNAME whitby.tvl.fyi. diff --git a/ops/dns/tvl.su.zone b/ops/dns/tvl.su.zone new file mode 100644 index 0000000000..da46752f13 --- /dev/null +++ b/ops/dns/tvl.su.zone @@ -0,0 +1,51 @@ +;; Google Cloud DNS for tvl.su. +;; +;; This zone is hosted in the project 'tvl-fyi', and registered via +;; NIC.RU. +;; +;; This zone is mostly identical to tvl.fyi and will eventually become +;; the primary zone. +tvl.su. 21600 IN SOA ns-cloud-b1.googledomains.com. cloud-dns-hostmaster.google.com. 33 21600 3600 259200 300 +tvl.su. 21600 IN NS ns-cloud-b1.googledomains.com. +tvl.su. 21600 IN NS ns-cloud-b2.googledomains.com. +tvl.su. 21600 IN NS ns-cloud-b3.googledomains.com. +tvl.su. 21600 IN NS ns-cloud-b4.googledomains.com. + +;; Landing website is hosted on whitby on the apex. +tvl.su. 21600 IN A 49.12.129.211 +tvl.su. 21600 IN AAAA 2a01:4f8:242:5b21:0:feed:edef:beef + +;; TVL infrastructure +whitby.tvl.su. 21600 IN A 49.12.129.211 +whitby.tvl.su. 21600 IN AAAA 2a01:4f8:242:5b21:0:feed:edef:beef + +;; TVL services +at.tvl.su. 21600 IN CNAME whitby.tvl.su. +atward.tvl.su. 21600 IN CNAME whitby.tvl.su. +b.tvl.su. 21600 IN CNAME whitby.tvl.su. +cache.tvl.su. 21600 IN CNAME whitby.tvl.su. +cl.tvl.su. 21600 IN CNAME whitby.tvl.su. +code.tvl.su. 21600 IN CNAME whitby.tvl.su. +cs.tvl.su. 21600 IN CNAME whitby.tvl.su. +images.tvl.su. 21600 IN CNAME whitby.tvl.su. +login.tvl.su. 21600 IN CNAME whitby.tvl.su. +static.tvl.su. 21600 IN CNAME whitby.tvl.su. +status.tvl.su. 21600 IN CNAME whitby.tvl.su. +todo.tvl.su. 21600 IN CNAME whitby.tvl.su. + +;; Google Workspaces domain verification +tvl.su. 21600 IN TXT "google-site-verification=3ksTBzFK3lZlzD3ddBfpaHs9qasfAiYBmvbW2T_ejH4" + +;; Google Workspaces email configuration +tvl.su. 21600 IN MX 1 aspmx.l.google.com. +tvl.su. 21600 IN MX 5 alt1.aspmx.l.google.com. +tvl.su. 21600 IN MX 5 alt2.aspmx.l.google.com. +tvl.su. 21600 IN MX 10 alt3.aspmx.l.google.com. +tvl.su. 21600 IN MX 10 alt4.aspmx.l.google.com. +tvl.su. 21600 IN TXT "v=spf1 include:_spf.google.com ~all" +google._domainkey.tvl.su. 21600 IN TXT ("v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlqCbnGa8oPwrudJK60l6MJj3NBnwj8wAPXNGtYy2SXrOBi7FT+ySwW7ATpfv6Xq9zGDUWJsENPUlFmvDiUs7Qi4scnNvSO1L+sDseB9/q1m3gMFVnTuieDO/" "T+KKkg0+uYgMM7YX5PahsAAJJ+EMb/r4afl3tcBMPR64VveKQ0hiSHA4zIYPsB9FB+b8S5C46uyY0r6WR7IzGjq2Gzb1do0kxvaKItTITWLSImcUu5ZZuXOUKJb441frVBWur5lXaYuedkxb1IRTTK0V/mBODE1D7k73MxGrqlzaMPdCqz+c3hRE18WVUkBTYjANVXDrs3yzBBVxaIAeu++vkO6BvQIDAQAB") + +;; Google Workspaces site aliases +docs.tvl.su. 21600 IN CNAME ghs.googlehosted.com. +groups.tvl.su. 21600 IN CNAME ghs.googlehosted.com. +mail.tvl.su. 21600 IN CNAME ghs.googlehosted.com. diff --git a/ops/gerrit-autosubmit/.gitignore b/ops/gerrit-autosubmit/.gitignore new file mode 100644 index 0000000000..2f7896d1d1 --- /dev/null +++ b/ops/gerrit-autosubmit/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/ops/gerrit-autosubmit/Cargo.lock b/ops/gerrit-autosubmit/Cargo.lock new file mode 100644 index 0000000000..7516c74034 --- /dev/null +++ b/ops/gerrit-autosubmit/Cargo.lock @@ -0,0 +1,302 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "crimp" +version = "4087.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ead2c83f7d1f9b8e5a6f7a25985d0d1759ccd2cd72abb1eee2db65d05e12b39" +dependencies = [ + "curl", + "serde", + "serde_json", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.68+curl-8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys", +] + +[[package]] +name = "gerrit-autosubmit" +version = "0.1.0" +dependencies = [ + "anyhow", + "crimp", + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/ops/gerrit-autosubmit/Cargo.toml b/ops/gerrit-autosubmit/Cargo.toml new file mode 100644 index 0000000000..fa51614a08 --- /dev/null +++ b/ops/gerrit-autosubmit/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gerrit-autosubmit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +crimp = "4087.0.0" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" diff --git a/ops/gerrit-autosubmit/default.nix b/ops/gerrit-autosubmit/default.nix new file mode 100644 index 0000000000..f69a9248e3 --- /dev/null +++ b/ops/gerrit-autosubmit/default.nix @@ -0,0 +1,7 @@ +{ depot, pkgs, ... }: + +depot.third_party.naersk.buildPackage { + src = ./.; + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ pkgs.openssl ]; +} diff --git a/ops/gerrit-autosubmit/src/main.rs b/ops/gerrit-autosubmit/src/main.rs new file mode 100644 index 0000000000..85d8a6af61 --- /dev/null +++ b/ops/gerrit-autosubmit/src/main.rs @@ -0,0 +1,194 @@ +//! gerrit-autosubmit connects to a Gerrit instance and submits the +//! longest chain of changes in which all ancestors are ready and +//! marked for autosubmit. +//! +//! It works like this: +//! +//! * it fetches all changes the Gerrit query API considers +//! submittable (i.e. all requirements fulfilled), and that have the +//! `Autosubmit` label set +//! +//! * it filters these changes down to those that are _actually_ +//! submittable (in Gerrit API terms: that have an active Submit button) +//! +//! * it filters out those that would submit ancestors that are *not* +//! marked with the `Autosubmit` label +//! +//! * it submits the longest chain +//! +//! After that it just loops. + +use anyhow::{Context, Result}; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::{thread, time}; + +mod gerrit { + use anyhow::{anyhow, Context, Result}; + use serde::Deserialize; + use serde_json::Value; + use std::collections::HashMap; + use std::env; + + pub struct Config { + gerrit_url: String, + username: String, + password: String, + } + + impl Config { + pub fn from_env() -> Result<Self> { + Ok(Config { + gerrit_url: env::var("GERRIT_URL") + .context("Gerrit base URL (no trailing slash) must be set in GERRIT_URL")?, + username: env::var("GERRIT_USERNAME") + .context("Gerrit username must be set in GERRIT_USERNAME")?, + password: env::var("GERRIT_PASSWORD") + .context("Gerrit password must be set in GERRIT_PASSWORD")?, + }) + } + } + + #[derive(Deserialize)] + pub struct ChangeInfo { + pub id: String, + pub revisions: HashMap<String, Value>, + } + + #[derive(Deserialize)] + pub struct Action { + #[serde(default)] + pub enabled: bool, + } + + const GERRIT_RESPONSE_PREFIX: &str = ")]}'"; + + pub fn get<T: serde::de::DeserializeOwned>(cfg: &Config, endpoint: &str) -> Result<T> { + let response = crimp::Request::get(&format!("{}/a{}", cfg.gerrit_url, endpoint)) + .user_agent("gerrit-autosubmit")? + .basic_auth(&cfg.username, &cfg.password)? + .send()? + .error_for_status(|r| anyhow!("request failed with status {}", r.status))?; + + let result: T = serde_json::from_slice(&response.body[GERRIT_RESPONSE_PREFIX.len()..])?; + Ok(result) + } + + pub fn submit(cfg: &Config, change_id: &str) -> Result<()> { + crimp::Request::post(&format!( + "{}/a/changes/{}/submit", + cfg.gerrit_url, change_id + )) + .user_agent("gerrit-autosubmit")? + .basic_auth(&cfg.username, &cfg.password)? + .send()? + .error_for_status(|r| anyhow!("submit failed with status {}", r.status))?; + + Ok(()) + } +} + +#[derive(Debug)] +struct SubmittableChange { + id: String, + revision: String, +} + +fn list_submittable(cfg: &gerrit::Config) -> Result<Vec<SubmittableChange>> { + let mut out = Vec::new(); + + let changes: Vec<gerrit::ChangeInfo> = gerrit::get( + &cfg, + "/changes/?q=is:submittable+label:Autosubmit+-is:wip+is:open&o=SKIP_DIFFSTAT&o=CURRENT_REVISION", + ) + .context("failed to list submittable changes")?; + + for change in changes.into_iter() { + out.push(SubmittableChange { + id: change.id, + revision: change + .revisions + .into_keys() + .next() + .context("change had no current revision")?, + }); + } + + Ok(out) +} + +fn is_submittable(cfg: &gerrit::Config, change: &SubmittableChange) -> Result<bool> { + let response: HashMap<String, gerrit::Action> = gerrit::get( + cfg, + &format!( + "/changes/{}/revisions/{}/actions", + change.id, change.revision + ), + ) + .context("failed to fetch actions for change")?; + + match response.get("submit") { + None => Ok(false), + Some(action) => Ok(action.enabled), + } +} + +fn submitted_with(cfg: &gerrit::Config, change_id: &str) -> Result<HashSet<String>> { + let response: Vec<gerrit::ChangeInfo> = + gerrit::get(cfg, &format!("/changes/{}/submitted_together", change_id)) + .context("failed to fetch related change list")?; + + Ok(response.into_iter().map(|c| c.id).collect()) +} + +fn autosubmit(cfg: &gerrit::Config) -> Result<bool> { + let mut submittable_changes: HashSet<String> = Default::default(); + + for change in list_submittable(&cfg)? { + if !is_submittable(&cfg, &change)? { + continue; + } + + submittable_changes.insert(change.id.clone()); + } + + let mut chains: BTreeMap<usize, String> = Default::default(); + for change_id in &submittable_changes { + let ancestors = submitted_with(&cfg, &change_id)?; + if ancestors.is_subset(&submittable_changes) { + chains.insert( + if ancestors.is_empty() { + 1 + } else { + ancestors.len() + }, + change_id.clone(), + ); + } + } + + // BTreeMap::last_key_value gives us the value associated with the + // largest key, i.e. with the longest submittable chain of changes. + if let Some((count, change_id)) = chains.last_key_value() { + println!( + "submitting change {} with chain length {}", + change_id, count + ); + + gerrit::submit(cfg, change_id).context("while submitting")?; + + Ok(true) + } else { + println!("nothing ready for autosubmit, waiting ..."); + Ok(false) + } +} + +fn main() -> Result<()> { + let cfg = gerrit::Config::from_env()?; + + loop { + if !autosubmit(&cfg)? { + thread::sleep(time::Duration::from_secs(30)); + } + } +} diff --git a/ops/gerrit-tvl/HttpModule.java b/ops/gerrit-tvl/HttpModule.java new file mode 100644 index 0000000000..6d785c0817 --- /dev/null +++ b/ops/gerrit-tvl/HttpModule.java @@ -0,0 +1,14 @@ +package su.tvl.gerrit; + +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.extensions.webui.JavaScriptPlugin; +import com.google.gerrit.extensions.webui.WebUiPlugin; +import com.google.inject.servlet.ServletModule; + +public final class HttpModule extends ServletModule { + + @Override + protected void configureServlets() { + DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("tvl.js")); + } +} diff --git a/ops/gerrit-tvl/MANIFEST.MF b/ops/gerrit-tvl/MANIFEST.MF new file mode 100644 index 0000000000..bfe4eedeb6 --- /dev/null +++ b/ops/gerrit-tvl/MANIFEST.MF @@ -0,0 +1,2 @@ +Gerrit-HttpModule: su.tvl.gerrit.HttpModule +Gerrit-PluginName: tvl diff --git a/ops/gerrit-tvl/README.md b/ops/gerrit-tvl/README.md new file mode 100644 index 0000000000..1b88600f19 --- /dev/null +++ b/ops/gerrit-tvl/README.md @@ -0,0 +1,6 @@ +# gerrit-tvl + +A Gerrit plugin that does TVL-specific things. + +You probably want to take inspiration from this rather than using it directly, +as it has a variety of TVL-ish assumptions baked into it. diff --git a/ops/gerrit-tvl/default.nix b/ops/gerrit-tvl/default.nix new file mode 100644 index 0000000000..f3bec7a3a2 --- /dev/null +++ b/ops/gerrit-tvl/default.nix @@ -0,0 +1,33 @@ +{ depot, pkgs, lib, ... }: + +let + classPath = lib.concatStringsSep ":" [ + "${depot.third_party.gerrit}/share/api/extension-api_deploy.jar" + ]; +in +pkgs.stdenvNoCC.mkDerivation rec { + name = "${pname}-${version}.jar"; + pname = "gerrit-tvl"; + version = "0.0.1"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + jdk + ]; + + buildPhase = '' + mkdir $NIX_BUILD_TOP/build + + # Build Java components. + export JAVAC="javac -cp ${classPath} -d $NIX_BUILD_TOP/build --release 11" + $JAVAC ./HttpModule.java + + # Install static files. + cp -R static $NIX_BUILD_TOP/build/static + ''; + + installPhase = '' + jar --create --file $out --manifest $src/MANIFEST.MF -C $NIX_BUILD_TOP/build . + ''; +} diff --git a/ops/gerrit-tvl/static/tvl.js b/ops/gerrit-tvl/static/tvl.js new file mode 100644 index 0000000000..684636de30 --- /dev/null +++ b/ops/gerrit-tvl/static/tvl.js @@ -0,0 +1,189 @@ +// vim: set noai ts=2 sw=2 et: */ + +// This is a read-only Buildkite token: it was generated by lukegb@, and has +// read_builds, read_build_logs, and read_pipelines permissions. +const BUILDKITE_TOKEN = 'a150658fb61062e432f13a032962d70fa9352088'; + +function encodeParams(p) { + const pieces = []; + for (let k of Object.getOwnPropertyNames(p)) { + pieces.push(`${encodeURIComponent(k)}=${encodeURIComponent(p[k])}`); + } + return pieces.join('&'); +} + +function formatDuration(from, to) { + const millisecondsTook = Math.floor(to.valueOf() - from.valueOf()); + if (millisecondsTook < 2000) return `${millisecondsTook} ms`; + const secondsTook = Math.floor(millisecondsTook / 1000); + if (secondsTook < 100) return `${secondsTook} seconds`; + const minutesTook = Math.floor(secondsTook / 60); + if (minutesTook < 60) return `${minutesTook} minutes`; + const hoursTook = Math.floor(minutesTook / 60); + const minutesRemainder = minutesTook - (hoursTook * 60); + return `${hoursTook}hr ${minutesRemainder}min`; +} + +// Maps the status of a Buildkite *job* to the statuses available for +// a Gerrit check. +// +// Note that jobs can have statuses that, according to the Buildkite +// documentation, are only available for builds, and maybe vice-versa. +// To deal with this we simply cover all statuses for all types here. +// +// Buildkite job statuses: https://buildkite.com/docs/pipelines/notifications#job-states +// +// Gerrit check statuses: https://gerrit.googlesource.com/gerrit/+/v3.4.0/polygerrit-ui/app/api/checks.ts#167 +// +// TODO(tazjin): Use SCHEDULED status once we have upgraded Gerrit +// past 3.4 +function jobStateToCheckRunStatus(state) { + const status = { + // Statuses documented for both types + 'blocked': 'RUNNABLE', + 'canceled': 'COMPLETED', + 'canceling': 'RUNNING', + 'running': 'RUNNING', + 'scheduled': 'RUNNABLE', + 'skipped': 'COMPLETED', + + // Statuses only documented for builds + 'creating': 'RUNNABLE', + 'failed': 'COMPLETED', + 'not_run': 'COMPLETED', + 'passed': 'COMPLETED', + + // Statuses only documented for jobs + 'accepted': 'RUNNABLE', + 'assigned': 'RUNNABLE', + 'blocked_failed': 'COMPLETED', + 'broken': 'COMPLETED', + 'finished': 'COMPLETED', + 'limited': 'RUNNABLE', + 'limiting': 'RUNNABLE', + 'pending': 'RUNNABLE', + 'timed_out': 'COMPLETED', + 'timing_out': 'RUNNING', + 'unblocked': 'RUNNABLE', + 'unblocked_failed': 'COMPLETED', + 'waiting': 'RUNNABLE', + 'waiting_failed': 'COMPLETED', + }[state]; + + if (!status) { + console.log(`unknown Buildkite job state: ${state}`); + } + + return status; +} + +const tvlChecksProvider = { + async fetch(change) { + let {patchsetSha, repo} = change; + + const experiments = window.ENABLED_EXPERIMENTS || []; + if (experiments.includes("UiFeature__tvl_check_debug")) { + patchsetSha = '76692104f58b849b1503a8d8a700298003fa7b5f'; + repo = 'depot'; + } + + if (repo !== 'depot') { + // We only handle TVL's depot at the moment. + return {responseCode: 'OK'}; + } + + const params = { + commit: patchsetSha, + }; + const url = `https://api.buildkite.com/v2/organizations/tvl/pipelines/depot/builds?${encodeParams(params)}`; + const resp = await fetch(url, { + headers: { + Authorization: `Bearer ${BUILDKITE_TOKEN}`, + }, + }); + const respJSON = await resp.json(); + + const runs = []; + for (let i = 0; i < respJSON.length; i++) { + const attempt = respJSON.length - i; + const build = respJSON[i]; + + for (let job of build.jobs) { + // Skip non-command jobs (e.g. waiting/grouping jobs) + if (job.type !== 'script') { + continue; + } + + // Skip jobs marked as 'broken' (this means they were skipped + // intentionally) + if (job.state === 'broken') { + continue; + } + + // TODO(lukegb): add the ability to retry these + const checkRun = { + patchset: parseInt(build.env.GERRIT_PATCHSET, 10), + attempt: attempt, + externalId: job.id, + checkName: job.name, + checkDescription: job.command, + checkLink: job.web_url, + status: jobStateToCheckRunStatus(job.state), + labelName: 'Verified', + }; + + if (job.scheduled_at) { + checkRun.scheduledTimestamp = new Date(job.scheduled_at); + } + + if (job.started_at) { + checkRun.startedTimestamp = new Date(job.started_at); + } + + if (job.finished_at) { + checkRun.finishedTimestamp = new Date(job.finished_at); + } + + let statusDescription = job.state; + if (checkRun.startedTimestamp && checkRun.finishedTimestamp) { + statusDescription = `${statusDescription} in ${formatDuration(checkRun.startedTimestamp, checkRun.finishedTimestamp)}`; + } else if (checkRun.startedTimestamp) { + statusDescription = `${statusDescription} for ${formatDuration(checkRun.startedTimestamp, new Date())}`; + } else if (checkRun.scheduledTimestamp) { + statusDescription = `${statusDescription} for ${formatDuration(checkRun.scheduledTimestamp, new Date())}`; + } + checkRun.statusDescription = statusDescription; + + if (['failed', 'timed_out'].includes(job.state)) { + const result = { + // TODO(lukegb): get the log as the message here (the Gerrit + // implementation doesn't yet seem to support newlines in message + // strings...) + links: [{ + url: job.web_url, + tooltip: "Buildkite", + primary: true, + icon: 'EXTERNAL', + }], + category: 'ERROR', + summary: `${job.command} failed`, + }; + checkRun.results = [result]; + } + + runs.push(checkRun); + } + } + + return { + responseCode: 'OK', + runs: runs, + }; + }, +}; + +Gerrit.install(plugin => { + console.log('TVL plugin initialising'); + + plugin.checks().register(tvlChecksProvider); +}); diff --git a/ops/glesys/.gitignore b/ops/glesys/.gitignore new file mode 100644 index 0000000000..de8e8f12ee --- /dev/null +++ b/ops/glesys/.gitignore @@ -0,0 +1,3 @@ +.terraform* +terraform.tfstate* +.envrc diff --git a/ops/glesys/README.md b/ops/glesys/README.md new file mode 100644 index 0000000000..00f61a9360 --- /dev/null +++ b/ops/glesys/README.md @@ -0,0 +1,20 @@ +Terraform for GleSYS +====================== + +This contains the Terraform configuration for deploying TVL's +infrastructure at [GleSYS](https://glesys.com). This includes object +storage (e.g. for backups and Terraform state) and DNS. + +Secrets are needed for applying this. The encrypted file +`//ops/secrets/tf-glesys.age` contains `export` calls which should be +sourced, for example via `direnv`, by users with the appropriate +credentials. + +An example `direnv` configuration used by tazjin is this: + +``` +# //ops/secrets/.envrc +source_up +eval $(age --decrypt -i ~/.ssh/id_ed25519 $(git rev-parse --show-toplevel)/ops/secrets/tf-glesys.age) +watch_file $(git rev-parse --show-toplevel)/secrets/tf-glesys.age +``` diff --git a/ops/glesys/default.nix b/ops/glesys/default.nix new file mode 100644 index 0000000000..e511e1f6b6 --- /dev/null +++ b/ops/glesys/default.nix @@ -0,0 +1,15 @@ +{ depot, lib, pkgs, ... }: + +depot.nix.readTree.drvTargets rec { + # Provide a Terraform wrapper with the right provider installed. + terraform = pkgs.terraform.withPlugins (_: [ + depot.third_party.terraform-provider-glesys + ]); + + validate = depot.tools.checks.validateTerraform { + inherit terraform; + name = "glesys"; + src = lib.cleanSource ./.; + env.GLESYS_TOKEN = "ci-dummy"; + }; +} diff --git a/ops/glesys/dns-nixery-dev.tf b/ops/glesys/dns-nixery-dev.tf new file mode 100644 index 0000000000..42bcec7e21 --- /dev/null +++ b/ops/glesys/dns-nixery-dev.tf @@ -0,0 +1,37 @@ +# DNS configuration for nixery.dev +# +# TODO(tazjin): Figure out what to do with //ops/dns for this. I'd +# like to keep zonefiles in case we move providers again, but maybe +# generate something from them. Not sure yet. + +resource "glesys_dnsdomain" "nixery_dev" { + name = "nixery.dev" +} + +resource "glesys_dnsdomain_record" "nixery_dev_apex_A" { + domain = glesys_dnsdomain.nixery_dev.id + host = "@" + type = "A" + data = "51.250.51.78" # nixery-01.tvl.fyi +} + +resource "glesys_dnsdomain_record" "nixery_dev_NS1" { + domain = glesys_dnsdomain.nixery_dev.id + host = "@" + type = "NS" + data = "ns1.namesystem.se." +} + +resource "glesys_dnsdomain_record" "nixery_dev_NS2" { + domain = glesys_dnsdomain.nixery_dev.id + host = "@" + type = "NS" + data = "ns2.namesystem.se." +} + +resource "glesys_dnsdomain_record" "nixery_dev_NS3" { + domain = glesys_dnsdomain.nixery_dev.id + host = "@" + type = "NS" + data = "ns3.namesystem.se." +} diff --git a/ops/glesys/dns-tvix-dev.tf b/ops/glesys/dns-tvix-dev.tf new file mode 100644 index 0000000000..296532a02b --- /dev/null +++ b/ops/glesys/dns-tvix-dev.tf @@ -0,0 +1,54 @@ +# DNS configuration for tvix.dev + +resource "glesys_dnsdomain" "tvix_dev" { + name = "tvix.dev" +} + +resource "glesys_dnsdomain_record" "tvix_dev_apex_A" { + domain = glesys_dnsdomain.tvix_dev.id + host = "@" + type = "A" + data = var.whitby_ipv4 +} + +resource "glesys_dnsdomain_record" "tvix_dev_apex_AAAA" { + domain = glesys_dnsdomain.tvix_dev.id + host = "@" + type = "AAAA" + data = var.whitby_ipv6 +} + +resource "glesys_dnsdomain_record" "tvix_dev_bolt_CNAME" { + domain = glesys_dnsdomain.tvix_dev.id + host = "bolt" + type = "CNAME" + data = "whitby.tvl.su." +} + +resource "glesys_dnsdomain_record" "tvix_dev_docs_CNAME" { + domain = glesys_dnsdomain.tvix_dev.id + host = "docs" + type = "CNAME" + data = "whitby.tvl.fyi." +} + +resource "glesys_dnsdomain_record" "tvix_dev_NS1" { + domain = glesys_dnsdomain.tvix_dev.id + host = "@" + type = "NS" + data = "ns1.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvix_dev_NS2" { + domain = glesys_dnsdomain.tvix_dev.id + host = "@" + type = "NS" + data = "ns2.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvix_dev_NS3" { + domain = glesys_dnsdomain.tvix_dev.id + host = "@" + type = "NS" + data = "ns3.namesystem.se." +} diff --git a/ops/glesys/dns-tvl-fyi.tf b/ops/glesys/dns-tvl-fyi.tf new file mode 100644 index 0000000000..9d7972c412 --- /dev/null +++ b/ops/glesys/dns-tvl-fyi.tf @@ -0,0 +1,113 @@ +# DNS configuration for tvl.fyi + +resource "glesys_dnsdomain" "tvl_fyi" { + name = "tvl.fyi" +} + +resource "glesys_dnsdomain_record" "tvl_fyi_NS1" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "NS" + data = "ns1.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_NS2" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "NS" + data = "ns2.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_NS3" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "NS" + data = "ns3.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_apex_A" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "A" + data = var.whitby_ipv4 +} + +resource "glesys_dnsdomain_record" "tvl_fyi_apex_AAAA" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "AAAA" + data = var.whitby_ipv6 +} + +resource "glesys_dnsdomain_record" "tvl_fyi_whitby_A" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "whitby" + type = "A" + data = var.whitby_ipv4 +} + +resource "glesys_dnsdomain_record" "tvl_fyi_whitby_AAAA" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "whitby" + type = "AAAA" + data = var.whitby_ipv6 +} + +resource "glesys_dnsdomain_record" "tvl_fyi_nixery-01_A" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "nixery-01" + type = "A" + data = "51.250.51.78" +} + +# Explicit records for all services running on whitby +resource "glesys_dnsdomain_record" "tvl_fyi_whitby_services" { + domain = glesys_dnsdomain.tvl_fyi.id + type = "CNAME" + data = "whitby.tvl.fyi." + host = each.key + for_each = toset(local.whitby_services) +} + +resource "glesys_dnsdomain_record" "tvl_fyi_net_CNAME" { + domain = glesys_dnsdomain.tvl_fyi.id + type = "CNAME" + data = "sanduny.tvl.su." + host = "net" +} + +# Google Domains mail forwarding configuration (no sending) +resource "glesys_dnsdomain_record" "tvl_fyi_MX_5" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "MX" + data = "5 gmr-smtp-in.l.google.com." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_MX_10" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "MX" + data = "10 alt1.gmr-smtp-in.l.google.com." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_MX_20" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "MX" + data = "20 alt2.gmr-smtp-in.l.google.com." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_MX_30" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "MX" + data = "30 alt3.aspmx.l.google.com." +} + +resource "glesys_dnsdomain_record" "tvl_fyi_MX_40" { + domain = glesys_dnsdomain.tvl_fyi.id + host = "@" + type = "MX" + data = "40 alt4.gmr-smtp-in.l.google.com." +} diff --git a/ops/glesys/dns-tvl-su.tf b/ops/glesys/dns-tvl-su.tf new file mode 100644 index 0000000000..f2286cf1cf --- /dev/null +++ b/ops/glesys/dns-tvl-su.tf @@ -0,0 +1,137 @@ +# DNS configuration for tvl.su + +resource "glesys_dnsdomain" "tvl_su" { + name = "tvl.su" +} + +resource "glesys_dnsdomain_record" "tvl_su_NS1" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "NS" + data = "ns1.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_su_NS2" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "NS" + data = "ns2.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_su_NS3" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "NS" + data = "ns3.namesystem.se." +} + +resource "glesys_dnsdomain_record" "tvl_su_apex_A" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "A" + data = var.whitby_ipv4 +} + +resource "glesys_dnsdomain_record" "tvl_su_apex_AAAA" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "AAAA" + data = var.whitby_ipv6 +} + +resource "glesys_dnsdomain_record" "tvl_su_whitby_A" { + domain = glesys_dnsdomain.tvl_su.id + host = "whitby" + type = "A" + data = var.whitby_ipv4 +} + +resource "glesys_dnsdomain_record" "tvl_su_whitby_AAAA" { + domain = glesys_dnsdomain.tvl_su.id + host = "whitby" + type = "AAAA" + data = var.whitby_ipv6 +} + +resource "glesys_dnsdomain_record" "tvl_su_sanduny_A" { + domain = glesys_dnsdomain.tvl_su.id + host = "sanduny" + type = "A" + data = var.sanduny_ipv4 +} + +resource "glesys_dnsdomain_record" "tvl_su_sanduny_AAAA" { + domain = glesys_dnsdomain.tvl_su.id + host = "sanduny" + type = "AAAA" + data = var.sanduny_ipv6 +} + +# Explicit records for all services running on whitby +resource "glesys_dnsdomain_record" "tvl_su_whitby_services" { + domain = glesys_dnsdomain.tvl_su.id + type = "CNAME" + data = "whitby.tvl.su." + host = each.key + for_each = toset(local.whitby_services) +} + +# historical tvixbolt.tvl.su record, redirects to bolt.tvix.dev +resource "glesys_dnsdomain_record" "tvix_su_tvixbolt_CNAME" { + domain = glesys_dnsdomain.tvl_su.id + host = "tvixbolt" + type = "CNAME" + data = "whitby.tvl.su." +} + +resource "glesys_dnsdomain_record" "tvl_su_inbox_CNAME" { + domain = glesys_dnsdomain.tvl_su.id + type = "CNAME" + data = "sanduny.tvl.su." + host = "inbox.tvl.su." +} + +resource "glesys_dnsdomain_record" "tvl_su_TXT_google_site" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "TXT" + data = "google-site-verification=3ksTBzFK3lZlzD3ddBfpaHs9qasfAiYBmvbW2T_ejH4" +} + +# Yandex 360 setup + +resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "TXT" + data = "yandex-verification: b99c43b7838949dc" +} + +resource "glesys_dnsdomain_record" "tvl_su_MX_yandex" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "MX" + data = "10 mx.yandex.net." +} + +resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex_spf" { + domain = glesys_dnsdomain.tvl_su.id + host = "@" + type = "TXT" + data = "v=spf1 redirect=_spf.yandex.net" + +} + +resource "glesys_dnsdomain_record" "tvl_su_TXT_yandex_dkim" { + domain = glesys_dnsdomain.tvl_su.id + host = "mail._domainkey" + type = "TXT" + data = "v=DKIM1; k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaRdWF8BtCHlTTQN8O+E5Qn27FVIpUEAdk1uq2vdIKh1Un/3NfdWtxStcS1Mf0iEprt1Fb4zgWOkDlPi+hH/UZqiC9QNeNqEBGMB9kgJyfsUt6cDCIVGvn8PT9JcZW1jxSziOj8nUWB4noqbaVcQNqNbwtaHPm3aifwKwScxVO7wIDAQAB" +} + +resource "glesys_dnsdomain_record" "tvl_su_CNAME_yandex_mail" { + domain = glesys_dnsdomain.tvl_su.id + host = "mail" + type = "CNAME" + data = "domain.mail.yandex.net." +} diff --git a/ops/glesys/main.tf b/ops/glesys/main.tf new file mode 100644 index 0000000000..ec6bb7c397 --- /dev/null +++ b/ops/glesys/main.tf @@ -0,0 +1,92 @@ +# Configure TVL resources hosted with GleSYS. +# +# Most importantly: +# - all of our DNS +# - object storage (e.g. backups) + +terraform { + required_providers { + glesys = { + source = "depot/glesys" + } + } + + backend "s3" { + endpoints = { + s3 = "https://objects.dc-sto1.glesys.net" + } + bucket = "tvl-state" + key = "terraform/tvl-glesys" + region = "glesys" + + skip_credentials_validation = true + skip_region_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + skip_s3_checksum = true + } +} + +provider "glesys" { + userid = "cl26117" # generated by GleSYS +} + +resource "glesys_objectstorage_instance" "tvl-backups" { + description = "tvl-backups" + datacenter = "dc-sto1" +} + +resource "glesys_objectstorage_instance" "tvl-state" { + description = "tvl-state" + datacenter = "dc-sto1" +} + +resource "glesys_objectstorage_credential" "terraform-state" { + instanceid = glesys_objectstorage_instance.tvl-state.id + description = "key for terraform state" +} + +resource "glesys_objectstorage_credential" "litestream" { + instanceid = glesys_objectstorage_instance.tvl-state.id + description = "key for litestream" +} + +variable "whitby_ipv4" { + type = string + default = "49.12.129.211" +} + +variable "whitby_ipv6" { + type = string + default = "2a01:4f8:242:5b21:0:feed:edef:beef" +} + +variable "sanduny_ipv4" { + type = string + default = "85.119.82.231" +} + +variable "sanduny_ipv6" { + type = string + default = "2001:ba8:1f1:f109::feed:edef:beef" +} + +locals { + # Hostnames of all public services on whitby + whitby_services = [ + "at", + "atward", + "auth", + "b", + "cache", + "cl", + "code", + "cs", + "deploys", + "images", + "signup", + "static", + "status", + "todo", + ] +} diff --git a/ops/journaldriver/Cargo.lock b/ops/journaldriver/Cargo.lock index 40bdc96280..97bbe16ceb 100644 --- a/ops/journaldriver/Cargo.lock +++ b/ops/journaldriver/Cargo.lock @@ -1,816 +1,646 @@ -[[package]] -name = "aho-corasick" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ascii" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "atty" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 [[package]] -name = "backtrace" -version = "0.3.9" +name = "aho-corasick" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ - "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] -name = "backtrace-sys" -version = "0.1.24" +name = "anyhow" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "base64" -version = "0.9.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" -version = "1.0.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] -name = "byteorder" -version = "1.2.6" +name = "build-env" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e068f31938f954b695423ecaf756179597627d0828c0d3e48c0a722a8b23cf9e" [[package]] name = "cc" -version = "1.0.25" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" -version = "0.1.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.6" +name = "crimp" +version = "4087.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ead2c83f7d1f9b8e5a6f7a25985d0d1759ccd2cd72abb1eee2db65d05e12b39" dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "curl", + "serde", + "serde_json", ] [[package]] -name = "chunked_transfer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cloudabi" -version = "0.0.3" +name = "cstr-argument" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "memchr", ] [[package]] -name = "cookie" -version = "0.11.0" +name = "curl" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", ] [[package]] -name = "core-foundation" -version = "0.5.1" +name = "curl-sys" +version = "0.4.68+curl-8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" dependencies = [ - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys", ] [[package]] -name = "core-foundation-sys" -version = "0.5.1" +name = "deranged" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "powerfmt", + "serde", ] [[package]] -name = "cstr-argument" -version = "0.0.2" +name = "env_logger" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] -name = "env_logger" -version = "0.5.13" +name = "errno" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "windows-sys", ] [[package]] -name = "failure" -version = "0.1.2" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types-shared 0.1.1", ] [[package]] -name = "failure_derive" -version = "0.1.2" +name = "foreign-types" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types-macros", + "foreign-types-shared 0.3.1", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] -name = "fuchsia-zircon-sys" +name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "humantime" -version = "1.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "idna" -version = "0.1.5" +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "rustix", + "windows-sys", ] [[package]] name = "itoa" -version = "0.4.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "journaldriver" -version = "1.1.0" +version = "5656.0.0" dependencies = [ - "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "medallion 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "systemd 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ureq 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "crimp", + "env_logger", + "lazy_static", + "log", + "medallion", + "pkg-config", + "serde", + "serde_json", + "systemd", + "time", ] [[package]] name = "lazy_static" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.43" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libsystemd-sys" -version = "0.2.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28ad38d7bee81aabd41201ee7d36df8d7f76aa0a455c77d5c365c4669b4b4b6" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "build-env", + "libc", + "pkg-config", ] [[package]] -name = "log" -version = "0.4.5" +name = "libz-sys" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "matches" -version = "0.1.8" +name = "linux-raw-sys" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] -name = "medallion" -version = "2.2.3" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "memchr" -version = "1.0.2" +name = "medallion" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b83c0c3277d722b53a6eb24e3c1321172f85b715cc7405add8ffd1f2f06288" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "base64", + "openssl", + "serde", + "serde_json", + "time", ] [[package]] name = "memchr" -version = "2.1.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] -name = "native-tls" -version = "0.2.1" +name = "once_cell" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "num-integer" -version = "0.1.39" +name = "openssl" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "num-traits" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl" -version = "0.10.12" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.36" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "pkg-config" -version = "0.3.14" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] -name = "proc-macro2" -version = "0.4.20" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "qstring" -version = "0.6.0" +name = "proc-macro2" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-ident", ] [[package]] -name = "quick-error" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "quote" -version = "0.6.8" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] -name = "rand" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_syscall" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_termios" -version = "0.1.1" +name = "regex" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex" -version = "1.0.5" +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ - "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] -name = "remove_dir_all" -version = "0.5.1" +name = "rustix" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", ] [[package]] -name = "rustc-demangle" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "ryu" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "safemem" -version = "0.3.0" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" -version = "0.1.14" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-sys", ] [[package]] -name = "security-framework" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "security-framework-sys" -version = "0.2.1" +name = "serde" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] name = "serde_derive" -version = "1.0.79" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.32" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] -name = "syn" -version = "0.14.9" +name = "socket2" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi", ] [[package]] name = "syn" -version = "0.15.8" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "synstructure" -version = "0.9.0" +name = "systemd" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95085b9c6eedbcf0b828302a3483a84bdbf772158e586b787092112008fd1f" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cstr-argument", + "foreign-types 0.5.0", + "libc", + "libsystemd-sys", + "log", + "utf8-cstr", ] [[package]] -name = "systemd" -version = "0.3.0" +name = "termcolor" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ - "cstr-argument 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "libsystemd-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-cstr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] -name = "tempfile" -version = "3.0.4" +name = "time" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", ] [[package]] -name = "termcolor" -version = "1.0.4" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] -name = "termion" -version = "1.5.1" +name = "time-macros" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time-core", ] [[package]] -name = "thread_local" -version = "0.3.6" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "time" -version = "0.1.40" +name = "utf8-cstr" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" [[package]] -name = "ucd-util" -version = "0.1.1" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "unicode-bidi" -version = "0.3.4" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "unicode-normalization" -version = "0.1.7" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "winapi-util" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] [[package]] -name = "ureq" -version = "0.6.2" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "qstring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "url" -version = "1.7.1" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-targets", ] [[package]] -name = "utf8-cstr" -version = "0.1.6" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "utf8-ranges" -version = "1.0.1" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "vcpkg" -version = "0.2.6" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "version_check" -version = "0.1.5" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "winapi" -version = "0.3.6" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "winapi-util" -version = "0.1.1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wincolor" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" -"checksum ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5fc969a8ce2c9c0c4b0429bb8431544f6658283c8326ba5ff8c762b75369335" -"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" -"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" -"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" -"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" -"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" -"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" -"checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf" -"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980" -"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa" -"checksum cstr-argument 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "514570a4b719329df37f93448a70df2baac553020d0eb43a8dfa9c1f5ba7b658" -"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" -"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9" -"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum libsystemd-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e751b723417158e0949ba470bee4affd6f1dd6b67622b5240d79186631b6a0d9" -"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum medallion 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2e6f0713b388174fc3de9b63a0a63dfcee191a8abc8e06c0a9c6d80821c1891" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b" -"checksum native-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0a7bd714e83db15676d31caf968ad7318e9cc35f93c85a90231c8f22867549" -"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" -"checksum openssl 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2e79eede055813a3ac52fb3915caf8e1c9da2dec1587871aec9f6f7b48508d" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.36 (registry+https://github.com/rust-lang/crates.io-index)" = "409d77eeb492a1aebd6eb322b2ee72ff7c7496b4434d98b3bf8be038755de65e" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" -"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee" -"checksum qstring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545ec057a36a93e25fb5883baed912e4984af4e2543bbf0e3463d962e0408469" -"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2069749032ea3ec200ca51e4a31df41759190a88edca0d2d86ee8bedf7073341" -"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" -"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" -"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" -"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" -"checksum schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1a231dc10abf6749cfa5d7767f25888d484201accbd919b66ab5413c502d56" -"checksum security-framework 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "697d3f3c23a618272ead9e1fb259c1411102b31c6af8b93f1d64cca9c3b0e8e0" -"checksum security-framework-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab01dfbe5756785b5b4d46e0289e5a18071dfa9a7c2b24213ea00b9ef9b665bf" -"checksum serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "84257ccd054dc351472528c8587b4de2dbf0dc0fe2e634030c1a90bfdacebaa9" -"checksum serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "31569d901045afbff7a9479f793177fe9259819aff10ab4f89ef69bbc5f567fe" -"checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce" -"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" -"checksum syn 0.15.8 (registry+https://github.com/rust-lang/crates.io-index)" = "356d1c5043597c40489e9af2d2498c7fefc33e99b7d75b43be336c8a59b3e45e" -"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" -"checksum systemd 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b62a732355787f960c25536210ae0a981aca2e5dae9dab8491bdae39613ce48" -"checksum tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "55c1195ef8513f3273d55ff59fe5da6940287a0d7a98331254397f464833675b" -"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum ureq 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f3f941c0434783c82e46d30508834be5f3c1f2c85dd1b98f0681984c7be8e03" -"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" -"checksum utf8-cstr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" -"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" -"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/ops/journaldriver/Cargo.toml b/ops/journaldriver/Cargo.toml index 248b22807f..65510d8705 100644 --- a/ops/journaldriver/Cargo.toml +++ b/ops/journaldriver/Cargo.toml @@ -1,21 +1,21 @@ [package] name = "journaldriver" -version = "1.1.0" -authors = ["Vincent Ambo <mail@tazj.in>"] +version = "5656.0.0" +authors = ["Vincent Ambo <tazjin@tvl.su>"] license = "GPL-3.0-or-later" +edition = "2021" [dependencies] -chrono = { version = "0.4", features = [ "serde" ]} -env_logger = "0.5" -failure = "0.1" -lazy_static = "1.0" +anyhow = "1.0" +crimp = "4087.0" +env_logger = "0.10" +lazy_static = "1.4" log = "0.4" -medallion = "2.2" -serde = "1.0" -serde_derive = "1.0" +medallion = "2.5" +serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" -systemd = "0.3" -ureq = { version = "0.6.2", features = [ "json" ]} +systemd = "0.5" +time = { version = "0.3", features = [ "serde-well-known", "macros" ]} [build-dependencies] pkg-config = "0.3" diff --git a/ops/journaldriver/build.rs b/ops/journaldriver/build.rs index d64c82a88a..79eb1001bf 100644 --- a/ops/journaldriver/build.rs +++ b/ops/journaldriver/build.rs @@ -1,6 +1,5 @@ extern crate pkg_config; fn main() { - pkg_config::probe_library("libsystemd") - .expect("Could not probe libsystemd"); + pkg_config::probe_library("libsystemd").expect("Could not probe libsystemd"); } diff --git a/ops/journaldriver/default.nix b/ops/journaldriver/default.nix index cc274094a9..2a3836c358 100644 --- a/ops/journaldriver/default.nix +++ b/ops/journaldriver/default.nix @@ -1,11 +1,11 @@ -{ depot, ... }: +{ depot, pkgs, ... }: -with depot.third_party; - -naersk.buildPackage { +depot.third_party.naersk.buildPackage { src = ./.; - buildInputs = [ - pkgconfig openssl systemd.dev + buildInputs = with pkgs; [ + pkg-config + openssl + systemd.dev ]; } diff --git a/ops/journaldriver/src/main.rs b/ops/journaldriver/src/main.rs index a57bb3505d..4c404e607e 100644 --- a/ops/journaldriver/src/main.rs +++ b/ops/journaldriver/src/main.rs @@ -31,44 +31,30 @@ //! `GOOGLE_APPLICATION_CREDENTIALS`, `GOOGLE_CLOUD_PROJECT` and //! `LOG_NAME` environment variables. -#[macro_use] extern crate failure; -#[macro_use] extern crate log; -#[macro_use] extern crate serde_derive; -#[macro_use] extern crate serde_json; -#[macro_use] extern crate lazy_static; - -extern crate chrono; -extern crate env_logger; -extern crate medallion; -extern crate serde; -extern crate systemd; -extern crate ureq; - -use chrono::offset::LocalResult; -use chrono::prelude::*; -use failure::ResultExt; -use serde_json::{from_str, Value}; -use std::env; -use std::fs::{self, File, rename}; -use std::io::{self, Read, ErrorKind, Write}; -use std::mem; +use anyhow::{bail, Context, Result}; +use lazy_static::lazy_static; +use log::{debug, error, info, trace}; +use serde::{Deserialize, Serialize}; +use serde_json::{from_str, json, Value}; +use std::convert::TryInto; +use std::fs::{self, rename, File}; +use std::io::{self, ErrorKind, Read, Write}; use std::path::PathBuf; -use std::process; use std::time::{Duration, Instant}; -use systemd::journal::*; +use std::{env, mem, process}; +use systemd::journal::{Journal, JournalFiles, JournalRecord, JournalSeek}; #[cfg(test)] mod tests; const LOGGING_SERVICE: &str = "https://logging.googleapis.com/google.logging.v2.LoggingServiceV2"; const ENTRIES_WRITE_URL: &str = "https://logging.googleapis.com/v2/entries:write"; -const METADATA_TOKEN_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"; +const METADATA_TOKEN_URL: &str = + "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"; const METADATA_ID_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/id"; const METADATA_ZONE_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/zone"; -const METADATA_PROJECT_URL: &str = "http://metadata.google.internal/computeMetadata/v1/project/project-id"; - -/// Convenience type alias for results using failure's `Error` type. -type Result<T> = std::result::Result<T, failure::Error>; +const METADATA_PROJECT_URL: &str = + "http://metadata.google.internal/computeMetadata/v1/project/project-id"; /// Representation of static service account credentials for GCP. #[derive(Debug, Deserialize)] @@ -126,32 +112,27 @@ lazy_static! { /// Convenience helper for retrieving values from the metadata server. fn get_metadata(url: &str) -> Result<String> { - let response = ureq::get(url) - .set("Metadata-Flavor", "Google") - .timeout_connect(5000) - .timeout_read(5000) - .call(); - - if response.ok() { - // Whitespace is trimmed to remove newlines from responses. - let body = response.into_string() - .context("Failed to decode metadata response")? - .trim().to_string(); - - Ok(body) - } else { - let status = response.status_line().to_string(); - let body = response.into_string() - .unwrap_or_else(|e| format!("Metadata body error: {}", e)); - bail!("Metadata failure: {} ({})", body, status) + let response = crimp::Request::get(url) + .header("Metadata-Flavor", "Google")? + .timeout(std::time::Duration::from_secs(5))? + .send()? + .as_string()?; + + if !response.is_success() { + bail!( + "Error response ({}) from metadata server: {}", + response.status, + response.body + ); } + + Ok(response.body.trim().to_owned()) } /// Convenience helper for determining the project ID. fn get_project_id() -> String { env::var("GOOGLE_CLOUD_PROJECT") - .map_err(Into::into) - .or_else(|_: failure::Error| get_metadata(METADATA_PROJECT_URL)) + .or_else(|_| get_metadata(METADATA_PROJECT_URL)) .expect("Could not determine project ID") } @@ -186,11 +167,9 @@ fn determine_monitored_resource() -> Value { } }) } else { - let instance_id = get_metadata(METADATA_ID_URL) - .expect("Could not determine instance ID"); + let instance_id = get_metadata(METADATA_ID_URL).expect("Could not determine instance ID"); - let zone = get_metadata(METADATA_ZONE_URL) - .expect("Could not determine instance zone"); + let zone = get_metadata(METADATA_ZONE_URL).expect("Could not determine instance zone"); json!({ "type": "gce_instance", @@ -252,9 +231,8 @@ fn get_metadata_token() -> Result<Token> { fn sign_service_account_token(credentials: &Credentials) -> Result<Token> { use medallion::{Algorithm, Header, Payload}; - let iat = Utc::now(); - let exp = iat.checked_add_signed(chrono::Duration::seconds(3600)) - .ok_or_else(|| format_err!("Failed to calculate token expiry"))?; + let iat = time::OffsetDateTime::now_utc(); + let exp = iat + time::Duration::seconds(3600); let header = Header { alg: Algorithm::RS256, @@ -267,8 +245,8 @@ fn sign_service_account_token(credentials: &Credentials) -> Result<Token> { iss: Some(credentials.client_email.clone()), sub: Some(credentials.client_email.clone()), aud: Some(LOGGING_SERVICE.to_string()), - iat: Some(iat.timestamp() as u64), - exp: Some(exp.timestamp() as u64), + iat: Some(iat.unix_timestamp().try_into().unwrap()), + exp: Some(exp.unix_timestamp().try_into().unwrap()), ..Default::default() }; @@ -323,7 +301,9 @@ enum Payload { /// text format. fn message_to_payload(message: Option<String>) -> Payload { match message { - None => Payload::TextPayload { text_payload: "empty log entry".into() }, + None => Payload::TextPayload { + text_payload: "empty log entry".into(), + }, Some(text_payload) => { // Attempt to deserialize the text payload as a generic // JSON value. @@ -333,7 +313,7 @@ fn message_to_payload(message: Option<String>) -> Payload { // expect other types of JSON payload) and return it // in that case. if json_payload.is_object() { - return Payload::JsonPayload { json_payload } + return Payload::JsonPayload { json_payload }; } } @@ -348,18 +328,15 @@ fn message_to_payload(message: Option<String>) -> Payload { /// Parse errors are dismissed and returned as empty options: There /// simply aren't any useful fallback mechanisms other than defaulting /// to ingestion time for journaldriver's use-case. -fn parse_microseconds(input: String) -> Option<DateTime<Utc>> { +fn parse_microseconds(input: String) -> Option<time::OffsetDateTime> { if input.len() != 16 { return None; } - let seconds: i64 = (&input[..10]).parse().ok()?; - let micros: u32 = (&input[10..]).parse().ok()?; + let micros: i128 = input.parse().ok()?; + let nanos: i128 = micros * 1000; - match Utc.timestamp_opt(seconds, micros * 1000) { - LocalResult::Single(time) => Some(time), - _ => None, - } + time::OffsetDateTime::from_unix_timestamp_nanos(nanos).ok() } /// Converts a journald log message priority to a @@ -408,7 +385,8 @@ struct LogEntry { labels: Value, #[serde(skip_serializing_if = "Option::is_none")] - timestamp: Option<DateTime<Utc>>, + #[serde(with = "time::serde::rfc3339::option")] + timestamp: Option<time::OffsetDateTime>, #[serde(flatten)] payload: Payload, @@ -450,9 +428,7 @@ impl From<JournalRecord> for LogEntry { // Journald uses syslogd's concept of priority. No idea if this is // always present, but it's optional in the Stackdriver API, so we just // omit it if we can't find or parse it. - let severity = record - .remove("PRIORITY") - .and_then(priority_to_severity); + let severity = record.remove("PRIORITY").and_then(priority_to_severity); LogEntry { payload, @@ -468,8 +444,7 @@ impl From<JournalRecord> for LogEntry { /// Attempt to read from the journal. If no new entry is present, /// await the next one up to the specified timeout. -fn receive_next_record(timeout: Duration, journal: &mut Journal) - -> Result<Option<JournalRecord>> { +fn receive_next_record(timeout: Duration, journal: &mut Journal) -> Result<Option<JournalRecord>> { let next_record = journal.next_record()?; if next_record.is_some() { return Ok(next_record); @@ -525,11 +500,10 @@ fn persist_cursor(cursor: String) -> Result<()> { if cursor.is_empty() { error!("Received empty journald cursor position, refusing to persist!"); error!("Please report this message at https://github.com/tazjin/journaldriver/issues/2"); - return Ok(()) + return Ok(()); } - let mut file = File::create(&*CURSOR_TMP_FILE) - .context("Failed to create cursor file")?; + let mut file = File::create(&*CURSOR_TMP_FILE).context("Failed to create cursor file")?; write!(file, "{}", cursor).context("Failed to write cursor file")?; @@ -547,13 +521,11 @@ fn persist_cursor(cursor: String) -> Result<()> { /// /// If flushing is successful the last cursor position will be /// persisted to disk. -fn flush(token: &mut Token, - entries: Vec<LogEntry>, - cursor: String) -> Result<()> { +fn flush(token: &mut Token, entries: Vec<LogEntry>, cursor: String) -> Result<()> { if token.is_expired() { debug!("Refreshing Google metadata access token"); let new_token = get_token()?; - mem::replace(token, new_token); + *token = new_token; } for chunk in entries.chunks(750) { @@ -583,25 +555,28 @@ fn prepare_request(entries: &[LogEntry]) -> Value { /// Perform the log entry insertion in Stackdriver Logging. fn write_entries(token: &Token, request: Value) -> Result<()> { - let response = ureq::post(ENTRIES_WRITE_URL) - .set("Authorization", format!("Bearer {}", token.token).as_str()) + let response = crimp::Request::post(ENTRIES_WRITE_URL) + .json(&request)? + .header("Authorization", format!("Bearer {}", token.token).as_str())? // The timeout values are set relatively high, not because of // an expectation of Stackdriver being slow but just to - // eventually hit an error case in case of network troubles. + // eventually force an error in case of network troubles. // Presumably no request in a functioning environment will // ever hit these limits. - .timeout_connect(2000) - .timeout_read(5000) - .send_json(request); + .timeout(std::time::Duration::from_secs(5))? + .send()?; - if response.ok() { - Ok(()) - } else { - let status = response.status_line().to_string(); - let body = response.into_string() - .unwrap_or_else(|_| "no response body".into()); - bail!("Write failure: {} ({})", body, status) + if !response.is_success() { + let status = response.status; + let body = response + .as_string() + .map(|r| r.body) + .unwrap_or_else(|_| "no valid response body".to_owned()); + + bail!("Writing to Stackdriver failed({}): {}", status, body); } + + Ok(()) } /// Attempt to read the initial cursor position from the configured @@ -624,14 +599,12 @@ fn initial_cursor() -> Result<JournalSeek> { Err(ref err) if err.kind() == ErrorKind::NotFound => { info!("No previous cursor position, reading from journal tail"); Ok(JournalSeek::Tail) - }, - Err(err) => { - (Err(err).context("Could not read cursor position"))? } + Err(err) => (Err(err).context("Could not read cursor position"))?, } } -fn main () { +fn main() { env_logger::init(); // The directory in which cursor positions are persisted should @@ -641,17 +614,17 @@ fn main () { process::exit(1); } - let cursor_position_dir = CURSOR_FILE.parent() + let cursor_position_dir = CURSOR_FILE + .parent() .expect("Invalid cursor position file path"); fs::create_dir_all(cursor_position_dir) .expect("Could not create directory to store cursor position in"); - let mut journal = Journal::open(JournalFiles::All, false, true) - .expect("Failed to open systemd journal"); + let mut journal = + Journal::open(JournalFiles::All, false, true).expect("Failed to open systemd journal"); - let seek_position = initial_cursor() - .expect("Failed to determine initial cursor position"); + let seek_position = initial_cursor().expect("Failed to determine initial cursor position"); match journal.seek(seek_position) { Ok(cursor) => info!("Opened journal at cursor '{}'", cursor), diff --git a/ops/journaldriver/src/tests.rs b/ops/journaldriver/src/tests.rs index 779add7a70..6f5045d6a5 100644 --- a/ops/journaldriver/src/tests.rs +++ b/ops/journaldriver/src/tests.rs @@ -1,5 +1,6 @@ use super::*; use serde_json::to_string; +use time::macros::datetime; #[test] fn test_text_entry_serialization() { @@ -15,7 +16,31 @@ fn test_text_entry_serialization() { let expected = "{\"labels\":null,\"textPayload\":\"test entry\"}"; let result = to_string(&entry).expect("serialization failed"); - assert_eq!(expected, result, "Plain text payload should serialize correctly") + assert_eq!( + expected, result, + "Plain text payload should serialize correctly" + ) +} + +#[test] +fn test_timestamped_entry_serialization() { + let entry = LogEntry { + labels: Value::Null, + timestamp: Some(datetime!(1952-10-07 12:00:00 UTC)), + payload: Payload::TextPayload { + text_payload: "test entry".into(), + }, + severity: None, + }; + + let expected = + "{\"labels\":null,\"timestamp\":\"1952-10-07T12:00:00Z\",\"textPayload\":\"test entry\"}"; + let result = to_string(&entry).expect("serialization failed"); + + assert_eq!( + expected, result, + "Plain text payload should serialize correctly" + ) } #[test] @@ -26,7 +51,7 @@ fn test_json_entry_serialization() { payload: Payload::JsonPayload { json_payload: json!({ "message": "JSON test" - }) + }), }, severity: None, }; @@ -34,7 +59,7 @@ fn test_json_entry_serialization() { let expected = "{\"labels\":null,\"jsonPayload\":{\"message\":\"JSON test\"}}"; let result = to_string(&entry).expect("serialization failed"); - assert_eq!(expected, result, "JSOn payload should serialize correctly") + assert_eq!(expected, result, "JSON payload should serialize correctly") } #[test] @@ -45,7 +70,10 @@ fn test_plain_text_payload() { text_payload: "plain text payload".into(), }; - assert_eq!(expected, payload, "Plain text payload should be detected correctly"); + assert_eq!( + expected, payload, + "Plain text payload should be detected correctly" + ); } #[test] @@ -55,7 +83,10 @@ fn test_empty_payload() { text_payload: "empty log entry".into(), }; - assert_eq!(expected, payload, "Empty payload should be handled correctly"); + assert_eq!( + expected, payload, + "Empty payload should be handled correctly" + ); } #[test] @@ -66,10 +97,13 @@ fn test_json_payload() { json_payload: json!({ "someKey": "someValue", "otherKey": 42 - }) + }), }; - assert_eq!(expected, payload, "JSON payload should be detected correctly"); + assert_eq!( + expected, payload, + "JSON payload should be detected correctly" + ); } #[test] @@ -82,14 +116,16 @@ fn test_json_no_object() { text_payload: "42".into(), }; - assert_eq!(expected, payload, "Non-object JSON payload should be plain text"); + assert_eq!( + expected, payload, + "Non-object JSON payload should be plain text" + ); } #[test] fn test_parse_microseconds() { let input: String = "1529175149291187".into(); - let expected: DateTime<Utc> = "2018-06-16T18:52:29.291187Z" - .to_string().parse().unwrap(); + let expected: time::OffsetDateTime = datetime!(2018-06-16 18:52:29.291187 UTC); assert_eq!(Some(expected), parse_microseconds(input)); } diff --git a/ops/keycloak/.gitignore b/ops/keycloak/.gitignore new file mode 100644 index 0000000000..017878c614 --- /dev/null +++ b/ops/keycloak/.gitignore @@ -0,0 +1,3 @@ +.terraform* +*.tfstate* +.envrc diff --git a/ops/keycloak/README.md b/ops/keycloak/README.md new file mode 100644 index 0000000000..fd72daa87d --- /dev/null +++ b/ops/keycloak/README.md @@ -0,0 +1,18 @@ +Terraform for Keycloak +====================== + +This contains the Terraform configuration for deploying TVL's Keycloak +instance (which lives at `auth.tvl.fyi`). + +Secrets are needed for applying this. The encrypted file +`//ops/secrets/tf-keycloak.age` contains `export` calls which should +be sourced, for example via `direnv`, by users with the appropriate +credentials. + +An example `direnv` configuration used by tazjin is this: + +``` +# //ops/keycloak/.envrc +source_up +eval $(age --decrypt -i ~/.ssh/id_ed25519 $(git rev-parse --show-toplevel)/ops/secrets/tf-keycloak.age) +``` diff --git a/ops/keycloak/clients.tf b/ops/keycloak/clients.tf new file mode 100644 index 0000000000..178971ae36 --- /dev/null +++ b/ops/keycloak/clients.tf @@ -0,0 +1,85 @@ +# All Keycloak clients, that is applications which authenticate +# through Keycloak. +# +# Includes first-party (i.e. TVL-hosted) and third-party clients. + +resource "keycloak_openid_client" "grafana" { + realm_id = keycloak_realm.tvl.id + client_id = "grafana" + name = "Grafana" + enabled = true + access_type = "CONFIDENTIAL" + standard_flow_enabled = true + base_url = "https://status.tvl.su" + + valid_redirect_uris = [ + "https://status.tvl.su/*", + ] +} + +resource "keycloak_openid_client" "gerrit" { + realm_id = keycloak_realm.tvl.id + client_id = "gerrit" + name = "TVL Gerrit" + enabled = true + access_type = "CONFIDENTIAL" + standard_flow_enabled = true + base_url = "https://cl.tvl.fyi" + description = "TVL's code review tool" + direct_access_grants_enabled = true + exclude_session_state_from_auth_response = false + + valid_redirect_uris = [ + "https://cl.tvl.fyi/*", + ] + + web_origins = [ + "https://cl.tvl.fyi", + ] +} + +resource "keycloak_saml_client" "buildkite" { + realm_id = keycloak_realm.tvl.id + client_id = "https://buildkite.com" + name = "Buildkite" + base_url = "https://buildkite.com/sso/tvl" + + client_signature_required = false + assertion_consumer_post_url = "https://buildkite.com/sso/~/1531aca5-f49c-4151-8832-a451e758af4c/saml/consume" + + valid_redirect_uris = [ + "https://buildkite.com/sso/~/1531aca5-f49c-4151-8832-a451e758af4c/saml/consume" + ] +} + +resource "keycloak_saml_user_attribute_protocol_mapper" "buildkite_email" { + realm_id = keycloak_realm.tvl.id + client_id = keycloak_saml_client.buildkite.id + name = "buildkite-email-mapper" + user_attribute = "email" + saml_attribute_name = "email" + saml_attribute_name_format = "Unspecified" +} + +resource "keycloak_saml_user_attribute_protocol_mapper" "buildkite_name" { + realm_id = keycloak_realm.tvl.id + client_id = keycloak_saml_client.buildkite.id + name = "buildkite-name-mapper" + user_attribute = "displayName" + saml_attribute_name = "name" + saml_attribute_name_format = "Unspecified" +} + +resource "keycloak_openid_client" "panettone" { + realm_id = keycloak_realm.tvl.id + client_id = "panettone" + name = "Panettone" + enabled = true + access_type = "CONFIDENTIAL" + standard_flow_enabled = true + + valid_redirect_uris = [ + "https://b.tvl.fyi/auth", + "http://localhost:6161/auth", + ] +} diff --git a/ops/keycloak/default.nix b/ops/keycloak/default.nix new file mode 100644 index 0000000000..94ed912dc9 --- /dev/null +++ b/ops/keycloak/default.nix @@ -0,0 +1,14 @@ +{ depot, lib, pkgs, ... }: + +depot.nix.readTree.drvTargets rec { + # Provide a Terraform wrapper with the right provider installed. + terraform = pkgs.terraform.withPlugins (p: [ + p.keycloak + ]); + + validate = depot.tools.checks.validateTerraform { + inherit terraform; + name = "keycloak"; + src = lib.cleanSource ./.; + }; +} diff --git a/ops/keycloak/main.tf b/ops/keycloak/main.tf new file mode 100644 index 0000000000..923ac19397 --- /dev/null +++ b/ops/keycloak/main.tf @@ -0,0 +1,44 @@ +# Configure TVL Keycloak instance. +# +# TODO(tazjin): Configure GitLab IDP + +terraform { + required_providers { + keycloak = { + source = "mrparkers/keycloak" + } + } + + backend "s3" { + endpoint = "https://objects.dc-sto1.glesys.net" + bucket = "tvl-state" + key = "terraform/tvl-keycloak" + region = "glesys" + + skip_credentials_validation = true + skip_region_validation = true + skip_metadata_api_check = true + } +} + +provider "keycloak" { + client_id = "terraform" + url = "https://auth.tvl.fyi" +} + +resource "keycloak_realm" "tvl" { + realm = "TVL" + enabled = true + display_name = "The Virus Lounge" + default_signature_algorithm = "RS256" + + smtp_server { + from = "tvlbot@tazj.in" + from_display_name = "The Virus Lounge" + host = "127.0.0.1" + port = "25" + reply_to = "depot@tvl.su" + ssl = false + starttls = false + } +} diff --git a/ops/keycloak/user_sources.tf b/ops/keycloak/user_sources.tf new file mode 100644 index 0000000000..01307fff8d --- /dev/null +++ b/ops/keycloak/user_sources.tf @@ -0,0 +1,44 @@ +# All user sources, that is services from which Keycloak gets user +# information (either by accessing a system like LDAP or integration +# through protocols like OIDC). + +variable "github_client_secret" { + type = string +} + +resource "keycloak_ldap_user_federation" "tvl_ldap" { + name = "tvl-ldap" + realm_id = keycloak_realm.tvl.id + enabled = true + connection_url = "ldap://localhost" + users_dn = "ou=users,dc=tvl,dc=fyi" + username_ldap_attribute = "cn" + uuid_ldap_attribute = "cn" + rdn_ldap_attribute = "cn" + full_sync_period = 86400 + trust_email = true + + user_object_classes = [ + "inetOrgPerson", + "organizationalPerson", + ] +} + +# keycloak_oidc_identity_provider.github will be destroyed +# (because keycloak_oidc_identity_provider.github is not in configuration) +resource "keycloak_oidc_identity_provider" "github" { + alias = "github" + provider_id = "github" + client_id = "6d7f8bb2e82bb6739556" + client_secret = var.github_client_secret + realm = keycloak_realm.tvl.id + backchannel_supported = false + gui_order = "1" + store_token = false + sync_mode = "IMPORT" + trust_email = true + + # These default to built-in values for the `github` provider_id. + authorization_url = "" + token_url = "" +} diff --git a/ops/kontemplate/README.md b/ops/kontemplate/README.md index 998e61a619..803a1c4f16 100644 --- a/ops/kontemplate/README.md +++ b/ops/kontemplate/README.md @@ -1,8 +1,8 @@ Kontemplate - A simple Kubernetes templater =========================================== -[Kontemplate][] is a simple CLI tool that can take sets of Kubernetes resource -files with placeholders and insert values per environment. +Kontemplate is a simple CLI tool that can take sets of Kubernetes resource files +with placeholders and insert values per environment. This tool was made because in many cases all I want in terms of Kubernetes configuration is simple value interpolation per environment (i.e. Kubernetes @@ -111,14 +111,14 @@ Releases are signed with the GPG key `DCF34CFAC1AC44B87E26333136EE34814F6D294A`. ### Building from source -You can clone Kontemplate either by cloning the full -[depot][https://git.tazj.in/] or by just cloning the kontemplate +You can clone Kontemplate either by cloning the full TVL +[depot][https://code.tvl.fyi] or by just cloning the kontemplate subtree like so: - git clone -b kontemplate https://git.tazj.in kontemplate + git clone https://code.tvl.fyi/depot.git:/ops/kontemplate.git The `go` tooling can be used as normal with this cloned repository. In -a full clone of the dpeot, Nix can be used to build Kontemplate: +a full clone of the depot, Nix can be used to build Kontemplate: nix-build -A ops.kontemplate @@ -181,6 +181,5 @@ in the `LICENSE` file. Please follow the [code of conduct](CODE_OF_CONDUCT.md). -[Kontemplate]: http://kontemplate.works [Helm]: https://helm.sh/ [releases page]: https://github.com/tazjin/kontemplate/releases diff --git a/ops/kontemplate/context/context_test.go b/ops/kontemplate/context/context_test.go index 7ecd9d587d..471eb246cf 100644 --- a/ops/kontemplate/context/context_test.go +++ b/ops/kontemplate/context/context_test.go @@ -333,15 +333,15 @@ func TestSetInvalidVariablesFromArguments(t *testing.T) { // This test ensures that variables are merged in the correct order. // Please consult the test data in `testdata/merging`. func TestValueMergePrecedence(t *testing.T) { - cliVars:= []string{"cliVar=cliVar"} + cliVars := []string{"cliVar=cliVar"} ctx, _ := LoadContext("testdata/merging/context.yaml", &cliVars) expected := map[string]interface{}{ "defaultVar": "defaultVar", - "importVar": "importVar", - "globalVar": "globalVar", + "importVar": "importVar", + "globalVar": "globalVar", "includeVar": "includeVar", - "cliVar": "cliVar", + "cliVar": "cliVar", } result := ctx.ResourceSets[0].Values diff --git a/ops/kontemplate/default.nix b/ops/kontemplate/default.nix index eb12906877..1190869c3f 100644 --- a/ops/kontemplate/default.nix +++ b/ops/kontemplate/default.nix @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2019 Vincent Ambo <mail@tazj.in> +# Copyright (C) 2016-2021 Vincent Ambo <mail@tazj.in> # # This file is part of Kontemplate. # @@ -10,15 +10,15 @@ # This file is the Nix derivation used to install Kontemplate on # Nix-based systems. -{ depot, ... }: +{ lib, pkgs, ... }: -with depot.third_party; buildGoPackage rec { +pkgs.buildGoPackage rec { name = "kontemplate-${version}"; version = "canon"; src = ./.; goPackagePath = "github.com/tazjin/kontemplate"; goDeps = ./deps.nix; - buildInputs = [ parallel ]; + buildInputs = [ pkgs.parallel ]; # Enable checks and configure check-phase to include vet: doCheck = true; diff --git a/ops/kontemplate/release.nix b/ops/kontemplate/release.nix index 8a04109526..6a3dbd5efe 100644 --- a/ops/kontemplate/release.nix +++ b/ops/kontemplate/release.nix @@ -10,13 +10,17 @@ # This file is the Nix derivation used to build release binaries for # several different architectures and operating systems. -let pkgs = import ((import <nixpkgs> {}).fetchFromGitHub { - owner = "NixOS"; - repo = "nixpkgs-channels"; - rev = "541d9cce8af7a490fb9085305939569567cb58e6"; - sha256 = "0jgz72hhzkd5vyq5v69vpljjlnf0lqaz7fh327bvb3cvmwbfxrja"; -}) {}; -in with pkgs; buildGoPackage rec { +let + pkgs = import + ((import <nixpkgs> { }).fetchFromGitHub { + owner = "NixOS"; + repo = "nixpkgs-channels"; + rev = "541d9cce8af7a490fb9085305939569567cb58e6"; + sha256 = "0jgz72hhzkd5vyq5v69vpljjlnf0lqaz7fh327bvb3cvmwbfxrja"; + }) + { }; +in +with pkgs; buildGoPackage rec { name = "kontemplate-${version}"; version = "canon"; src = ./.; @@ -29,8 +33,8 @@ in with pkgs; buildGoPackage rec { # reason for setting the 'allowGoReference' flag. dontStrip = true; # Linker configuration handles stripping allowGoReference = true; - CGO_ENABLED="0"; - GOCACHE="off"; + CGO_ENABLED = "0"; + GOCACHE = "off"; # Configure release builds via the "build-matrix" script: buildInputs = [ git ]; diff --git a/ops/kontemplate/templater/templater_test.go b/ops/kontemplate/templater/templater_test.go index c20858c203..9d9fc8d1ff 100644 --- a/ops/kontemplate/templater/templater_test.go +++ b/ops/kontemplate/templater/templater_test.go @@ -185,7 +185,7 @@ func TestInsertTemplateFunction(t *testing.T) { resourceSet := context.ResourceSet{ Path: "testdata", Values: map[string]interface{}{ - "testName": "TestInsertTemplateFunction", + "testName": "TestInsertTemplateFunction", }, } diff --git a/ops/machines/all-systems.nix b/ops/machines/all-systems.nix new file mode 100644 index 0000000000..c4382fbddb --- /dev/null +++ b/ops/machines/all-systems.nix @@ -0,0 +1,27 @@ +{ depot, ... }: + +(with depot.ops.machines; [ + sanduny + whitby +]) ++ + +(with depot.users.tazjin.nixos; [ + camden + frog + tverskoy + zamalek +]) ++ + +(with depot.users.aspen.system.system; [ + yeren + mugwump + ogopogo + lusca +]) ++ + +(with depot.users.wpcarro.nixos; [ + ava + kyoko + marcus + tarasco +]) diff --git a/ops/machines/nixery-01/default.nix b/ops/machines/nixery-01/default.nix new file mode 100644 index 0000000000..c99db214d8 --- /dev/null +++ b/ops/machines/nixery-01/default.nix @@ -0,0 +1,40 @@ +# nixery.dev backing host in ru-central1-b +{ depot, lib, pkgs, ... }: # readTree options +{ config, ... }: # passed by module system + +let + mod = name: depot.path.origSrc + ("/ops/modules/" + name); +in +{ + imports = [ + (mod "known-hosts.nix") + (mod "nixery.nix") + (mod "tvl-users.nix") + (mod "www/nixery.dev.nix") + (mod "yandex-cloud.nix") + + (depot.third_party.agenix.src + "/modules/age.nix") + ]; + + networking = { + hostName = "nixery-01"; + domain = "tvl.fyi"; + firewall.allowedTCPPorts = [ 22 80 443 ]; + }; + + security.sudo.extraRules = lib.singleton { + groups = [ "wheel" ]; + commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }]; + }; + + services.depot.nixery.enable = true; + + # Automatically collect garbage from the Nix store. + services.depot.automatic-gc = { + enable = true; + interval = "1 hour"; + diskThreshold = 25; # GiB + maxFreed = 150; # GiB + preserveGenerations = "30d"; + }; +} diff --git a/ops/machines/sanduny/default.nix b/ops/machines/sanduny/default.nix new file mode 100644 index 0000000000..af2dfb02a5 --- /dev/null +++ b/ops/machines/sanduny/default.nix @@ -0,0 +1,138 @@ +# sanduny.tvl.su +# +# This is a VPS hosted with Bitfolk, intended to additionally serve +# some of our public services like cgit, josh and the websites. +# +# In case of whitby going down, sanduny will keep depot available. + +_: # ignore readTree options + +{ config, depot, lib, pkgs, ... }: + +let + mod = name: depot.path.origSrc + ("/ops/modules/" + name); +in +{ + imports = [ + (mod "cgit.nix") + (mod "depot-inbox.nix") + (mod "depot-replica.nix") + (mod "journaldriver.nix") + (mod "known-hosts.nix") + (mod "tvl-cache.nix") + (mod "tvl-headscale.nix") + (mod "tvl-users.nix") + (mod "www/inbox.tvl.su.nix") + (mod "www/self-redirect.nix") + (mod "www/volgasprint.org.nix") + ]; + + networking = { + hostName = "sanduny"; + domain = "tvl.su"; + useDHCP = false; + + interfaces.eth0 = { + ipv4.addresses = lib.singleton { + address = "85.119.82.231"; + prefixLength = 21; + }; + + ipv6.addresses = lib.singleton { + address = "2001:ba8:1f1:f109::feed:edef:beef"; + prefixLength = 64; + }; + }; + + defaultGateway = "85.119.80.1"; + defaultGateway6.address = "2001:ba8:1f1:f109::1"; + + firewall.allowedTCPPorts = [ 22 80 443 ]; + + # https://bitfolk.com/customer_information.html#toc_2_DNS + nameservers = [ + "85.119.80.232" + "85.119.80.233" + "2001:ba8:1f1:f205::53" + "2001:ba8:1f1:f206::53" + ]; + }; + + security.sudo.wheelNeedsPassword = false; + + environment.systemPackages = with pkgs; [ + emacs-nox + vim + curl + unzip + htop + ]; + + programs.mtr.enable = true; + + services.openssh.enable = true; + services.fail2ban.enable = true; + + # Run tailscale for the TVL net.tvl.fyi network. + # tailscale up --login-server https://net.tvl.fyi --accept-dns=false --advertise-exit-node + services.tailscale = { + enable = true; + useRoutingFeatures = "server"; # for exit-node usage + }; + + # Automatically collect garbage from the Nix store. + services.depot.automatic-gc = { + enable = true; + interval = "1 hour"; + diskThreshold = 2; # GiB + maxFreed = 5; # GiB + preserveGenerations = "90d"; + }; + + # Allow Gerrit to replicate depot to /var/lib/depot + services.depot.replica.enable = true; + + # Run git serving tools locally ... + services.depot.cgit = { + enable = true; + repo = "/var/lib/depot"; + }; + + # Serve public-inbox ... + services.depot.inbox.enable = true; + + time.timeZone = "UTC"; + + # GRUB does not actually need to be installed on disk; Bitfolk have + # their own way of booting systems as long as config is in place. + boot.loader.grub.device = "nodev"; + boot.loader.grub.enable = true; + boot.initrd.availableKernelModules = [ "xen_blkfront" ]; + + hardware.cpu.intel.updateMicrocode = true; + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/aabc3638-43ca-45f3-af89-c451e8448e92"; + fsType = "ext4"; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/75aa99d5-fed7-4c5c-8570-7745f6cff9f5"; + fsType = "ext3"; + }; + + "/nix" = { + device = "/dev/disk/by-uuid/d1721678-c294-482b-b72e-3b15f2c56c63"; + fsType = "ext4"; + }; + }; + + tvl.cache.enable = true; + + swapDevices = lib.singleton { + device = "/dev/disk/by-uuid/df4ad9da-0a06-4c27-93e5-5d44e4750e55"; + }; + + system.stateVersion = "22.05"; # Did you read the comment? +} diff --git a/ops/machines/whitby/OWNERS b/ops/machines/whitby/OWNERS new file mode 100644 index 0000000000..4581a80d61 --- /dev/null +++ b/ops/machines/whitby/OWNERS @@ -0,0 +1,5 @@ +set noparent + +# Want in on this list? Try paying! +lukegb +tazjin diff --git a/ops/nixos/whitby/README.md b/ops/machines/whitby/README.md index 55287c5412..55287c5412 100644 --- a/ops/nixos/whitby/README.md +++ b/ops/machines/whitby/README.md diff --git a/ops/machines/whitby/default.nix b/ops/machines/whitby/default.nix new file mode 100644 index 0000000000..6a8ee56abc --- /dev/null +++ b/ops/machines/whitby/default.nix @@ -0,0 +1,652 @@ +{ depot, lib, pkgs, ... }: # readTree options +{ config, ... }: # passed by module system + +let + inherit (builtins) listToAttrs; + inherit (lib) range; + + mod = name: depot.path.origSrc + ("/ops/modules/" + name); +in +{ + imports = [ + (mod "atward.nix") + (mod "cgit.nix") + (mod "clbot.nix") + (mod "gerrit-autosubmit.nix") + (mod "irccat.nix") + (mod "josh.nix") + (mod "journaldriver.nix") + (mod "known-hosts.nix") + (mod "livegrep.nix") + (mod "monorepo-gerrit.nix") + (mod "owothia.nix") + (mod "panettone.nix") + (mod "paroxysm.nix") + (mod "restic.nix") + (mod "smtprelay.nix") + (mod "sourcegraph.nix") + (mod "tvl-buildkite.nix") + (mod "tvl-slapd/default.nix") + (mod "tvl-users.nix") + (mod "www/atward.tvl.fyi.nix") + (mod "www/auth.tvl.fyi.nix") + (mod "www/b.tvl.fyi.nix") + (mod "www/cache.tvl.su.nix") + (mod "www/cl.tvl.fyi.nix") + (mod "www/code.tvl.fyi.nix") + (mod "www/cs.tvl.fyi.nix") + (mod "www/deploys.tvl.fyi.nix") + (mod "www/self-redirect.nix") + (mod "www/signup.tvl.fyi.nix") + (mod "www/static.tvl.fyi.nix") + (mod "www/status.tvl.su.nix") + (mod "www/todo.tvl.fyi.nix") + (mod "www/tvix.dev.nix") + (mod "www/tvl.fyi.nix") + (mod "www/tvl.su.nix") + (mod "www/wigglydonke.rs.nix") + + # experimental! + (mod "www/grep.tvl.fyi.nix") + + (depot.third_party.agenix.src + "/modules/age.nix") + ]; + + hardware = { + enableRedistributableFirmware = true; + cpu.amd.updateMicrocode = true; + }; + + boot = { + tmp.useTmpfs = true; + kernelModules = [ "kvm-amd" ]; + supportedFilesystems = [ "zfs" ]; + + initrd = { + availableKernelModules = [ + "igb" + "xhci_pci" + "nvme" + "ahci" + "usbhid" + "usb_storage" + "sr_mod" + ]; + + # Enable SSH in the initrd so that we can enter disk encryption + # passwords remotely. + network = { + enable = true; + ssh = { + enable = true; + port = 2222; + authorizedKeys = + depot.users.tazjin.keys.all + ++ depot.users.lukegb.keys.all + ++ [ depot.users.aspen.keys.whitby ]; + + hostKeys = [ + /etc/secrets/initrd_host_ed25519_key + ]; + }; + + # this will launch the zfs password prompt on login and kill the + # other prompt + postCommands = '' + echo "zfs load-key -a && killall zfs" >> /root/.profile + ''; + }; + }; + + kernel.sysctl = { + "net.ipv4.tcp_congestion_control" = "bbr"; + }; + + loader.grub = { + enable = true; + efiSupport = true; + efiInstallAsRemovable = true; + device = "/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_S439NA0N201620"; + }; + + zfs.requestEncryptionCredentials = true; + }; + + fileSystems = { + "/" = { + device = "zroot/root"; + fsType = "zfs"; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/073E-7FBD"; + fsType = "vfat"; + }; + + "/nix" = { + device = "zroot/nix"; + fsType = "zfs"; + }; + + "/home" = { + device = "zroot/home"; + fsType = "zfs"; + }; + }; + + networking = { + # Glass is boring, but Luke doesn't like Wapping - the Prospect of + # Whitby, however, is quite a pleasant establishment. + hostName = "whitby"; + domain = "tvl.fyi"; + hostId = "b38ca543"; + useDHCP = false; + + # Don't use Hetzner's DNS servers. + nameservers = [ + "8.8.8.8" + "8.8.4.4" + ]; + + defaultGateway6 = { + address = "fe80::1"; + interface = "enp196s0"; + }; + + firewall.allowedTCPPorts = [ 22 80 443 4238 8443 29418 ]; + firewall.allowedUDPPorts = [ 8443 ]; + + interfaces.enp196s0.useDHCP = true; + interfaces.enp196s0.ipv6.addresses = [ + { + address = "2a01:04f8:0242:5b21::feed:edef:beef"; + prefixLength = 64; + } + ]; + }; + + # Generate an immutable /etc/resolv.conf from the nameserver settings + # above (otherwise DHCP overwrites it): + environment.etc."resolv.conf" = with lib; { + source = pkgs.writeText "resolv.conf" '' + ${concatStringsSep "\n" (map (ns: "nameserver ${ns}") config.networking.nameservers)} + options edns0 + ''; + }; + + # Disable background git gc system-wide, as it has a tendency to break CI. + environment.etc."gitconfig".source = pkgs.writeText "gitconfig" '' + [gc] + autoDetach = false + ''; + + time.timeZone = "UTC"; + + nix = { + nrBuildUsers = 256; + settings = { + max-jobs = lib.mkDefault 64; + secret-key-files = "/run/agenix/nix-cache-priv"; + + trusted-users = [ + "aspen" + "lukegb" + "tazjin" + "sterni" + ]; + }; + + sshServe = { + enable = true; + keys = with depot.users; + tazjin.keys.all + ++ lukegb.keys.all + ++ [ aspen.keys.whitby ] + ++ sterni.keys.all + ; + }; + }; + + programs.mtr.enable = true; + programs.mosh.enable = true; + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + # Configure secrets for services that need them. + age.secrets = + let + secretFile = name: depot.ops.secrets."${name}.age"; + in + { + clbot.file = secretFile "clbot"; + gerrit-autosubmit.file = secretFile "gerrit-autosubmit"; + grafana.file = secretFile "grafana"; + irccat.file = secretFile "irccat"; + keycloak-db.file = secretFile "keycloak-db"; + nix-cache-priv.file = secretFile "nix-cache-priv"; + owothia.file = secretFile "owothia"; + panettone.file = secretFile "panettone"; + smtprelay.file = secretFile "smtprelay"; + + buildkite-agent-token = { + file = secretFile "buildkite-agent-token"; + mode = "0440"; + group = "buildkite-agents"; + }; + + buildkite-graphql-token = { + file = secretFile "buildkite-graphql-token"; + mode = "0440"; + group = "buildkite-agents"; + }; + + buildkite-besadii-config = { + file = secretFile "besadii"; + mode = "0440"; + group = "buildkite-agents"; + }; + + buildkite-private-key = { + file = secretFile "buildkite-ssh-private-key"; + mode = "0440"; + group = "buildkite-agents"; + }; + + gerrit-besadii-config = { + file = secretFile "besadii"; + owner = "git"; + }; + + gerrit-secrets = { + file = secretFile "gerrit-secrets"; + path = "/var/lib/gerrit/etc/secure.config"; + owner = "git"; + mode = "0400"; + }; + + clbot-ssh = { + file = secretFile "clbot-ssh"; + owner = "clbot"; + }; + + # Not actually a secret + nix-cache-pub = { + file = secretFile "nix-cache-pub"; + mode = "0444"; + }; + + depot-replica-key = { + file = secretFile "depot-replica-key"; + mode = "0500"; + owner = "git"; + group = "git"; + path = "/var/lib/git/.ssh/id_ed25519"; + }; + }; + + # Automatically collect garbage from the Nix store. + services.depot.automatic-gc = { + enable = true; + interval = "1 hour"; + diskThreshold = 200; # GiB + maxFreed = 420; # GiB + preserveGenerations = "90d"; + }; + + # Run a handful of Buildkite agents to support parallel builds. + services.depot.buildkite = { + enable = true; + agentCount = 32; + }; + + # Start a local SMTP relay to Gmail (used by gerrit) + services.depot.smtprelay = { + enable = true; + args = { + listen = ":2525"; + remote_host = "smtp.gmail.com:587"; + remote_auth = "plain"; + remote_user = "tvlbot@tazj.in"; + }; + }; + + # Start a ZNC instance which bounces for tvlbot and owothia. + services.znc = { + enable = true; + useLegacyConfig = false; + config = { + LoadModule = [ + "webadmin" + "adminlog" + ]; + + User.admin = { + Admin = true; + Pass.password = { + Method = "sha256"; + Hash = "bb00aa8239de484c2925b1c3f6a196fb7612633f001daa9b674f83abe7e1103f"; + Salt = "TiB0Ochb1CrtpMTl;2;j"; + }; + }; + + Listener.l = { + Host = "localhost"; + Port = 2627; # bncr + SSL = false; + }; + }; + }; + + # Start the Gerrit->IRC bot + services.depot.clbot = { + enable = true; + channels = [ "#tvix-dev" "#tvl" ]; + + # See //fun/clbot for details. + flags = { + gerrit_host = "cl.tvl.fyi:29418"; + gerrit_ssh_auth_username = "clbot"; + gerrit_ssh_auth_key = config.age.secretsDir + "/clbot-ssh"; + + irc_server = "localhost:${toString config.services.znc.config.Listener.l.Port}"; + irc_user = "tvlbot"; + irc_nick = "tvlbot"; + + notify_branches = "canon,refs/meta/config"; + notify_repo = "depot"; + + # This secret is read from an environment variable, which is + # populated by a systemd EnvironmentFile. + irc_pass = "$CLBOT_PASS"; + }; + }; + + services.depot = { + # Run a SourceGraph code search instance + sourcegraph.enable = true; + + # Run a livegrep code search instance + livegrep.enable = true; + + # Run the Panettone issue tracker + panettone = { + enable = true; + dbUser = "panettone"; + dbName = "panettone"; + irccatChannel = "#tvl"; + }; + + # Run the first cursed bot (quote bot) + paroxysm.enable = true; + + # Run the second cursed bot + owothia = { + enable = true; + ircServer = "localhost"; + ircPort = config.services.znc.config.Listener.l.Port; + }; + + # Run irccat to forward messages to IRC + irccat = { + enable = true; + config = { + tcp.listen = ":4722"; # "ircc" + irc = { + server = "localhost:${toString config.services.znc.config.Listener.l.Port}"; + tls = false; + nick = "tvlbot"; + # Note: irccat means 'ident' where it says 'realname', so + # this is critical for connecting to ZNC. + realname = "tvlbot"; + channels = [ + "#tvl" + ]; + }; + }; + }; + + # Run atward, the search engine redirection thing. + atward.enable = true; + + # Run cgit & josh to serve git + cgit = { + enable = true; + user = "git"; # run as the same user as gerrit + }; + + josh.enable = true; + + # Configure backups to GleSYS + restic = { + enable = true; + paths = [ + "/var/backup/postgresql" + "/var/lib/grafana" + "/var/lib/znc" + ]; + }; + + # Run autosubmit bot for Gerrit + gerrit-autosubmit.enable = true; + }; + + services.postgresql = { + enable = true; + enableTCPIP = true; + package = pkgs.postgresql_16; + + authentication = lib.mkForce '' + local all all trust + host all all 127.0.0.1/32 password + host all all ::1/128 password + hostnossl all all 127.0.0.1/32 password + hostnossl all all ::1/128 password + ''; + + ensureDatabases = [ + "panettone" + ]; + + ensureUsers = [{ + name = "panettone"; + ensureDBOwnership = true; + }]; + }; + + services.postgresqlBackup = { + enable = true; + databases = [ + "keycloak" + "panettone" + "tvldb" + ]; + }; + + services.nix-serve = { + enable = true; + port = 6443; + secretKeyFile = config.age.secretsDir + "/nix-cache-priv"; + bindAddress = "localhost"; + }; + + services.fail2ban.enable = true; + + environment.systemPackages = (with pkgs; [ + bat + bb + curl + direnv + emacs-nox + fd + git + htop + hyperfine + jq + nano + nvd + ripgrep + tree + unzip + vim + zfs + zfstools + ]) ++ (with depot; [ + ops.deploy-whitby + ]); + + # Required for prometheus to be able to scrape stats + services.nginx.statusPage = true; + + # Configure Prometheus & Grafana. Exporter configuration for + # Prometheus is inside the respective service modules. + services.prometheus = { + enable = true; + retentionTime = "90d"; + + exporters = { + node = { + enable = true; + + enabledCollectors = [ + "logind" + "processes" + "systemd" + ]; + }; + + nginx = { + enable = true; + sslVerify = false; + constLabels = [ "host=whitby" ]; + }; + }; + + scrapeConfigs = [{ + job_name = "node"; + scrape_interval = "5s"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; + }]; + } + { + job_name = "nginx"; + scrape_interval = "5s"; + static_configs = [{ + targets = [ "localhost:${toString config.services.prometheus.exporters.nginx.port}" ]; + }]; + }]; + }; + + services.grafana = { + enable = true; + + settings = { + server = { + http_port = 4723; # "graf" on phone keyboard + domain = "status.tvl.su"; + root_url = "https://status.tvl.su"; + }; + + analytics.reporting_enabled = false; + + "auth.generic_oauth" = { + enabled = true; + client_id = "grafana"; + scopes = "openid profile email"; + name = "TVL"; + email_attribute_path = "mail"; + login_attribute_path = "sub"; + name_attribute_path = "displayName"; + auth_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/auth"; + token_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/token"; + api_url = "https://auth.tvl.fyi/auth/realms/TVL/protocol/openid-connect/userinfo"; + + # Give lukegb, aspen, tazjin "Admin" rights. + role_attribute_path = "((sub == 'lukegb' || sub == 'aspen' || sub == 'tazjin') && 'Admin') || 'Editor'"; + + # Allow creating new Grafana accounts from OAuth accounts. + allow_sign_up = true; + }; + + "auth.anonymous" = { + enabled = true; + org_name = "The Virus Lounge"; + org_role = "Viewer"; + }; + + "auth.basic".enabled = false; + + auth = { + oauth_auto_login = true; + disable_login_form = true; + }; + }; + + provision = { + enable = true; + datasources.settings.datasources = [{ + name = "Prometheus"; + type = "prometheus"; + url = "http://localhost:9090"; + }]; + }; + }; + + # Contains GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET. + systemd.services.grafana.serviceConfig.EnvironmentFile = config.age.secretsDir + "/grafana"; + + services.keycloak = { + enable = true; + + settings = { + http-port = 5925; # kycl + hostname = "auth.tvl.fyi"; + http-relative-path = "/auth"; + proxy = "edge"; + }; + + database = { + type = "postgresql"; + passwordFile = config.age.secretsDir + "/keycloak-db"; + createLocally = false; + }; + }; + + # Join TVL Tailscale network at net.tvl.fyi + services.tailscale = { + enable = true; + useRoutingFeatures = "server"; # for exit-node usage + }; + + # Allow Keycloak access to the LDAP module by forcing in the JVM + # configuration + systemd.services.keycloak.environment.PREPEND_JAVA_OPTS = + "--add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED"; + + security.sudo.extraRules = [ + { + groups = [ "wheel" ]; + commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }]; + } + ]; + + users = { + # Set up a user & group for git shenanigans + groups.git = { }; + users.git = { + group = "git"; + isSystemUser = true; + createHome = true; + home = "/var/lib/git"; + }; + }; + + zramSwap.enable = true; + + system.stateVersion = "20.03"; +} diff --git a/ops/nixos/.skip-subtree b/ops/modules/.skip-subtree index 09520f8c83..09520f8c83 100644 --- a/ops/nixos/.skip-subtree +++ b/ops/modules/.skip-subtree diff --git a/ops/nixos/README.md b/ops/modules/README.md index 595b4c3344..595b4c3344 100644 --- a/ops/nixos/README.md +++ b/ops/modules/README.md diff --git a/ops/modules/atward.nix b/ops/modules/atward.nix new file mode 100644 index 0000000000..f345a08e31 --- /dev/null +++ b/ops/modules/atward.nix @@ -0,0 +1,38 @@ +{ depot, config, lib, pkgs, ... }: + +let + cfg = config.services.depot.atward; + description = "atward - (attempt to) cleverly route queries"; +in +{ + options.services.depot.atward = { + enable = lib.mkEnableOption description; + + host = lib.mkOption { + type = lib.types.str; + default = "[::1]"; + description = "Host on which atward should listen"; + }; + + port = lib.mkOption { + type = lib.types.int; + default = 28973; + description = "Port on which atward should listen"; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.atward = { + inherit description; + script = "${depot.web.atward}/bin/atward"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + Restart = "always"; + }; + + environment.ATWARD_LISTEN_ADDRESS = "${cfg.host}:${toString cfg.port}"; + }; + }; +} diff --git a/ops/modules/auto-deploy.nix b/ops/modules/auto-deploy.nix new file mode 100644 index 0000000000..c504906b2b --- /dev/null +++ b/ops/modules/auto-deploy.nix @@ -0,0 +1,104 @@ +# Defines a service for automatically and periodically calling depot's +# rebuild-system on a NixOS machine. +# +# Deploys can be stopped in emergency situations by creating an empty +# file called `stop` in the state directory of the auto-deploy service +# (typically /var/lib/auto-deploy). +{ depot, config, lib, pkgs, ... }: + +let + cfg = config.services.depot.auto-deploy; + description = "to automatically rebuild the current system's NixOS config from the latest checkout of depot"; + + rebuild-system = depot.ops.nixos.rebuildSystemWith "$STATE_DIRECTORY/deploy"; + deployScript = pkgs.writeShellScript "auto-deploy" '' + set -ueo pipefail + + if [[ $EUID -ne 0 ]]; then + echo "Oh no! Only root is allowed to run auto-deploy!" >&2 + exit 1 + fi + + if [[ -f $STATE_DIRECTORY/stop ]]; then + echo "stop file exists in $STATE_DIRECTORY, not deploying!" >&2 + exit 1 + fi + + readonly depot=$STATE_DIRECTORY/depot.git + readonly deploy=$STATE_DIRECTORY/deploy + readonly git="git -C $depot" + + # find-or-create depot + if [ ! -d $depot ]; then + # cannot use $git here because $depot doesn't exist + git clone --bare ${cfg.git-remote} $depot + fi + + function cleanup() { + $git worktree remove $deploy + } + trap cleanup EXIT + + $git fetch origin + $git worktree add --force $deploy FETCH_HEAD + # unsure why, but without this switch-to-configuration attempts to install + # NixOS in $STATE_DIRECTORY + (cd / && ${rebuild-system}/bin/rebuild-system) + ''; +in +{ + options.services.depot.auto-deploy = { + enable = lib.mkEnableOption description; + + git-remote = lib.mkOption { + type = lib.types.str; + default = "https://cl.tvl.fyi/depot.git"; + description = '' + The (possibly remote) repository from which to clone as specified by the + GIT URLS section of `man git-clone`. + ''; + }; + + interval = lib.mkOption { + type = lib.types.str; + example = "1h"; + description = '' + Interval between Nix builds, specified in systemd.time(7) format. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.auto-deploy = { + inherit description; + script = "${deployScript}"; + path = with pkgs; [ + bash + git + gnutar + gzip + ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + # We need to prevent NixOS from interrupting us while it attempts to + # restart systemd units. + restartIfChanged = false; + + serviceConfig = { + Type = "oneshot"; + StateDirectory = "auto-deploy"; + }; + }; + + systemd.timers.auto-deploy = { + inherit description; + wantedBy = [ "multi-user.target" ]; + + timerConfig = { + OnActiveSec = "1"; + OnUnitActiveSec = cfg.interval; + }; + }; + }; +} diff --git a/ops/modules/automatic-gc.nix b/ops/modules/automatic-gc.nix new file mode 100644 index 0000000000..003f160919 --- /dev/null +++ b/ops/modules/automatic-gc.nix @@ -0,0 +1,97 @@ +# Defines a service for automatically collecting Nix garbage +# periodically, without relying on the (ostensibly broken) Nix options +# for min/max space available. +{ config, lib, pkgs, ... }: + +let + cfg = config.services.depot.automatic-gc; + description = "Automatically collect Nix garbage"; + + GiBtoKiB = n: n * 1024 * 1024; + GiBtoBytes = n: n * 1024 * 1024 * 1024; + + gcScript = pkgs.writeShellScript "automatic-nix-gc" '' + set -ueo pipefail + + if [ -e /run/stop-automatic-gc ]; then + echo "GC is disabled through /run/stop-automatic-gc" + exit 0 + fi + + readonly MIN_THRESHOLD_KIB="${toString (GiBtoKiB cfg.diskThreshold)}" + readonly MAX_FREED_BYTES="${toString (GiBtoBytes cfg.maxFreed)}" + readonly GEN_THRESHOLD="${cfg.preserveGenerations}" + readonly AVAILABLE_KIB=$(df --sync /nix --output=avail | tail -n1) + + if [ "''${AVAILABLE_KIB}" -lt "''${MIN_THRESHOLD_KIB}" ]; then + echo "Have ''${AVAILABLE_KIB} KiB, but want ''${MIN_THRESHOLD_KIB} KiB." + echo "Triggering Nix garbage collection up to ''${MAX_FREED_BYTES} bytes." + set -x + ${config.nix.package}/bin/nix-collect-garbage \ + --delete-older-than "''${GEN_THRESHOLD}" \ + --max-freed "''${MAX_FREED_BYTES}" + else + echo "Skipping GC, enough space available" + fi + ''; +in +{ + options.services.depot.automatic-gc = { + enable = lib.mkEnableOption description; + + interval = lib.mkOption { + type = lib.types.str; + example = "1h"; + description = '' + Interval between garbage collection runs, specified in + systemd.time(7) format. + ''; + }; + + diskThreshold = lib.mkOption { + type = lib.types.int; + example = "100"; + description = '' + Minimum amount of space that needs to be available (in GiB) on + the partition holding /nix. Garbage collection is triggered if + it falls below this. + ''; + }; + + maxFreed = lib.mkOption { + type = lib.types.int; + example = "420"; + description = '' + Maximum amount of space to free in a single GC run, in GiB. + ''; + }; + + preserveGenerations = lib.mkOption { + type = lib.types.str; + default = "90d"; + description = '' + Preserve NixOS generations younger than the specified value, + in the format expected by nix-collect-garbage(1). + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.automatic-gc = { + inherit description; + script = "${gcScript}"; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.automatic-gc = { + inherit description; + requisite = [ "nix-daemon.service" ]; + wantedBy = [ "multi-user.target" ]; + + timerConfig = { + OnActiveSec = "1"; + OnUnitActiveSec = cfg.interval; + }; + }; + }; +} diff --git a/ops/modules/btrfs-auto-scrub.nix b/ops/modules/btrfs-auto-scrub.nix new file mode 100644 index 0000000000..748bb75c5f --- /dev/null +++ b/ops/modules/btrfs-auto-scrub.nix @@ -0,0 +1,25 @@ +# Automatically performs a scrub on all btrfs filesystems configured in +# `config.fileSystems` on a daily schedule (by default). Activated by importing. +{ config, lib, ... }: + +{ + config = { + services = { + btrfs.autoScrub = { + enable = true; + interval = lib.mkDefault "*-*-* 03:30:00"; + # gather all btrfs fileSystems, extra ones can be added via the NixOS + # module merging mechanism, of course. + fileSystems = lib.concatLists ( + lib.mapAttrsToList + ( + _: + { fsType, mountPoint, ... }: + if fsType == "btrfs" then [ mountPoint ] else [ ] + ) + config.fileSystems + ); + }; + }; + }; +} diff --git a/ops/modules/cgit.nix b/ops/modules/cgit.nix new file mode 100644 index 0000000000..fc3f171585 --- /dev/null +++ b/ops/modules/cgit.nix @@ -0,0 +1,55 @@ +# Configuration for running the TVL cgit instance using thttpd. +{ config, depot, lib, pkgs, ... }: + +let + cfg = config.services.depot.cgit; + + userConfig = + if builtins.isNull cfg.user then { + DynamicUser = true; + } else { + User = cfg.user; + Group = cfg.user; + }; +in +{ + options.services.depot.cgit = with lib; { + enable = mkEnableOption "Run cgit web interface for depot"; + + port = mkOption { + description = "Port on which cgit should listen"; + type = types.int; + default = 2448; + }; + + repo = mkOption { + description = "Path to depot's .git folder on the machine"; + type = types.str; + default = "/var/lib/gerrit/git/depot.git/"; + }; + + user = mkOption { + description = '' + User to use for the cgit service. It is expected that this is + also the name of the user's primary group. + ''; + + type = with types; nullOr str; + default = null; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.cgit = { + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Restart = "on-failure"; + + ExecStart = depot.web.cgit-tvl.override { + inherit (cfg) port repo; + }; + } // userConfig; + }; + }; +} diff --git a/ops/nixos/clbot.nix b/ops/modules/clbot.nix index 0c45badd2b..bdddff6c81 100644 --- a/ops/nixos/clbot.nix +++ b/ops/modules/clbot.nix @@ -1,9 +1,9 @@ # Module that configures CLBot, our Gerrit->IRC info bridge. -{ config, lib, pkgs, ... }: +{ depot, config, lib, pkgs, ... }: let inherit (builtins) attrValues concatStringsSep mapAttrs readFile; - inherit (pkgs) runCommandNoCC; + inherit (pkgs) runCommand; inherit (lib) listToAttrs @@ -21,7 +21,7 @@ let (attrValues (mapAttrs (key: value: "-${key} \"${toString value}\"") flags)); # Escapes a unit name for use in systemd - systemdEscape = name: removeSuffix "\n" (readFile (runCommandNoCC "unit-name" {} '' + systemdEscape = name: removeSuffix "\n" (readFile (runCommand "unit-name" { } '' ${pkgs.systemd}/bin/systemd-escape '${name}' >> $out '')); @@ -31,18 +31,19 @@ let description = "${description} to ${channel}"; wantedBy = [ "multi-user.target" ]; - script = "${config.depot.fun.clbot}/bin/clbot ${mkFlags (cfg.flags // { + script = "${depot.fun.clbot}/bin/clbot ${mkFlags (cfg.flags // { irc_channel = channel; })} -alsologtostderr"; serviceConfig = { User = "clbot"; - EnvironmentFile = "/etc/secrets/clbot"; + EnvironmentFile = cfg.secretsFile; Restart = "always"; }; }; }; -in { +in +{ options.services.depot.clbot = { enable = mkEnableOption description; @@ -55,6 +56,12 @@ in { type = with types; listOf str; description = "Channels in which to post (generates one unit per channel)"; }; + + secretsFile = mkOption { + type = types.str; + description = "EnvironmentFile from which to load secrets"; + default = config.age.secretsDir + "/clbot"; + }; }; config = mkIf cfg.enable { @@ -62,11 +69,11 @@ in { # (notably the SSH private key) readable by this user outside of # the module. users = { - groups.clbot = {}; + groups.clbot = { }; users.clbot = { group = "clbot"; - isNormalUser = false; + isSystemUser = true; }; }; diff --git a/ops/modules/default-imports.nix b/ops/modules/default-imports.nix new file mode 100644 index 0000000000..11514a437a --- /dev/null +++ b/ops/modules/default-imports.nix @@ -0,0 +1,14 @@ +{ depot, ... }: + +# Default set of modules that are imported in all Depot nixos systems +# +# All modules here should be properly gated behind a `lib.mkEnableOption` with a +# `lib.mkIf` for the config. + +{ + imports = [ + ./automatic-gc.nix + ./auto-deploy.nix + ./tvl-cache.nix + ]; +} diff --git a/ops/modules/default.nix b/ops/modules/default.nix new file mode 100644 index 0000000000..d747e8e131 --- /dev/null +++ b/ops/modules/default.nix @@ -0,0 +1,2 @@ +# Make readTree happy at this level. +_: { } diff --git a/ops/modules/depot-inbox.nix b/ops/modules/depot-inbox.nix new file mode 100644 index 0000000000..14fc646a9a --- /dev/null +++ b/ops/modules/depot-inbox.nix @@ -0,0 +1,148 @@ +# public-inbox configuration for depot@tvl.su +# +# The account itself is a Yandex 360 account in the tvl.su organisation, which +# is accessed via IMAP. Yandex takes care of spam filtering for us, so there is +# no particular SpamAssassin or other configuration. +{ config, depot, lib, pkgs, ... }: + +let + cfg = config.services.depot.inbox; + + imapConfig = pkgs.writeText "offlineimaprc" '' + [general] + accounts = depot + + [Account depot] + localrepository = Local + remoterepository = Remote + + [Repository Local] + type = Maildir + localfolders = /var/lib/public-inbox/depot-imap + + [Repository Remote] + type = IMAP + ssl = yes + sslcacertfile = /etc/ssl/certs/ca-bundle.crt + remotehost = imap.yandex.ru + remoteuser = depot@tvl.su + remotepassfile = /var/run/agenix/depot-inbox-imap + ''; +in +{ + options.services.depot.inbox = with lib; { + enable = mkEnableOption "Enable public-inbox for depot@tvl.su"; + + depotPath = mkOption { + description = "path to local depot replica"; + type = types.str; + default = "/var/lib/depot"; + }; + }; + + config = lib.mkIf cfg.enable { + # Having nginx *and* other services use ACME certificates for the + # same hostname is unsupported in NixOS without resorting to doing + # all ACME configuration manually. + # + # To work around this, we duplicate the TLS certificate used by + # nginx to a location that is readable by public-inbox daemons. + systemd.services.inbox-cert-sync = { + startAt = "daily"; + + script = '' + ${pkgs.coreutils}/bin/install -D -g ${config.users.groups."public-inbox".name} -m 0440 \ + /var/lib/acme/inbox.tvl.su/fullchain.pem /var/lib/public-inbox/tls/fullchain.pem + + ${pkgs.coreutils}/bin/install -D -g ${config.users.groups."public-inbox".name} -m 0440 \ + /var/lib/acme/inbox.tvl.su/key.pem /var/lib/public-inbox/tls/key.pem + ''; + }; + + services.public-inbox = { + enable = true; + + http.enable = true; + http.port = 8053; + + imap = { + enable = true; + port = 993; + cert = "/var/lib/public-inbox/tls/fullchain.pem"; + key = "/var/lib/public-inbox/tls/key.pem"; + }; + + nntp = { + enable = true; + port = 563; + cert = "/var/lib/public-inbox/tls/fullchain.pem"; + key = "/var/lib/public-inbox/tls/key.pem"; + }; + + inboxes.depot = rec { + address = [ + "depot@tvl.su" # primary address + "depot@tazj.in" # legacy address + ]; + + description = "TVL depot development (mail to depot@tvl.su)"; + coderepo = [ "depot" ]; + url = "https://inbox.tvl.su/depot"; + + watch = [ + "maildir:/var/lib/public-inbox/depot-imap/INBOX/" + ]; + + newsgroup = "su.tvl.depot"; + }; + + settings.coderepo.depot = { + dir = cfg.depotPath; + cgitUrl = "https://code.tvl.fyi"; + }; + + settings.publicinbox = { + wwwlisting = "all"; + nntpserver = [ "inbox.tvl.su" ]; + imapserver = [ "inbox.tvl.su" ]; + + depot.obfuscate = true; + noObfuscate = [ + "tvl.su" + "tvl.fyi" + ]; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 993 # imap + 563 # nntp + ]; + + age.secrets.depot-inbox-imap = { + file = depot.ops.secrets."depot-inbox-imap.age"; + mode = "0440"; + group = config.users.groups."public-inbox".name; + }; + + systemd.services.offlineimap-depot = { + description = "download mail for depot@tvl.su"; + wantedBy = [ "multi-user.target" ]; + startAt = "minutely"; + + script = '' + mkdir -p /var/lib/public-inbox/depot-imap + ${pkgs.offlineimap}/bin/offlineimap -c ${imapConfig} + ''; + + serviceConfig = { + Type = "oneshot"; + + # Run in the same user context as public-inbox itself to avoid + # permissions trouble. + User = config.users.users."public-inbox".name; + Group = config.users.groups."public-inbox".name; + }; + }; + }; +} diff --git a/ops/modules/depot-replica.nix b/ops/modules/depot-replica.nix new file mode 100644 index 0000000000..b71f10409a --- /dev/null +++ b/ops/modules/depot-replica.nix @@ -0,0 +1,45 @@ +# Configuration for receiving a depot replica from Gerrit's +# replication plugin. +# +# This only prepares the user and folder for receiving the replica, +# but Gerrit configuration still needs to be modified in addition. +{ config, depot, lib, pkgs, ... }: + +let + cfg = config.services.depot.replica; +in +{ + options.services.depot.replica = with lib; { + enable = mkEnableOption "Receive depot git replica from Gerrit"; + + key = mkOption { + description = "Public key to use for replication"; + type = types.str; + default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFFab9O1xaQ1TCyn+CxmXHexdlLzURREG+UR3Qdi3BvH"; + }; + + path = mkOption { + description = "Replication destination path (will be created)"; + type = types.str; + default = "/var/lib/depot"; + }; + }; + + config = lib.mkIf cfg.enable { + users.groups.depot = { }; + + users.users.depot = { + group = "depot"; + isSystemUser = true; + createHome = true; + home = cfg.path; + homeMode = "755"; # everyone can read depot + openssh.authorizedKeys.keys = lib.singleton cfg.key; + shell = pkgs.bashInteractive; # gerrit needs to run shell commands + }; + + environment.systemPackages = [ + pkgs.git + ]; + }; +} diff --git a/ops/modules/gerrit-autosubmit.nix b/ops/modules/gerrit-autosubmit.nix new file mode 100644 index 0000000000..34342c8d55 --- /dev/null +++ b/ops/modules/gerrit-autosubmit.nix @@ -0,0 +1,43 @@ +# Configuration for the Gerrit autosubmit bot (//ops/gerrit-autosubmit) +{ depot, pkgs, config, lib, ... }: + +let + cfg = config.services.depot.gerrit-autosubmit; + description = "gerrit-autosubmit - autosubmit bot for Gerrit"; + mkStringOption = default: lib.mkOption { + inherit default; + type = lib.types.str; + }; +in +{ + options.services.depot.gerrit-autosubmit = { + enable = lib.mkEnableOption description; + gerritUrl = mkStringOption "https://cl.tvl.fyi"; + + secretsFile = with lib; mkOption { + description = "Path to a systemd EnvironmentFile containing secrets"; + default = config.age.secretsDir + "/gerrit-autosubmit"; + type = types.str; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.gerrit-autosubmit = { + inherit description; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + ExecStart = "${depot.ops.gerrit-autosubmit}/bin/gerrit-autosubmit"; + DynamicUser = true; + Restart = "always"; + EnvironmentFile = cfg.secretsFile; + }; + + environment = { + GERRIT_URL = cfg.gerritUrl; + }; + }; + }; +} diff --git a/ops/nixos/irccat.nix b/ops/modules/irccat.nix index 68735e4ce5..2263118d99 100644 --- a/ops/nixos/irccat.nix +++ b/ops/modules/irccat.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ depot, config, lib, pkgs, ... }: let cfg = config.services.depot.irccat; @@ -11,37 +11,50 @@ let # then recursively merge it with an on-disk secret using jq on # service launch. configJson = pkgs.writeText "irccat.json" (builtins.toJSON cfg.config); - configMerge = pkgs.writeShellScript "merge-irccat-config" '' - if [ ! -f "/etc/secrets/irccat.json" ]; then + + # Right now, merging configuration file with secrets and running the main + # application needs to happen both in ExecStart=, due to + # https://github.com/systemd/systemd/issues/19604#issuecomment-989279884 + mergeAndLaunch = pkgs.writeShellScript "merge-irccat-config" '' + if [ ! -f "$CREDENTIALS_DIRECTORY/secrets" ]; then echo "irccat secrets file is missing" exit 1 fi # jq's * is the recursive merge operator - ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${configJson} /etc/secrets/irccat.json \ + ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${configJson} "$CREDENTIALS_DIRECTORY/secrets" \ > /var/lib/irccat/irccat.json + + exec ${depot.third_party.irccat}/bin/irccat ''; -in { +in +{ options.services.depot.irccat = { enable = lib.mkEnableOption description; config = lib.mkOption { - type = lib.types.attrs; # varying value types + type = lib.types.attrsOf lib.types.anything; # varying value types description = "Configuration structure (unchecked!)"; }; + + secretsFile = lib.mkOption { + type = lib.types.str; + description = "Path to the secrets file to be merged"; + default = config.age.secretsDir + "/irccat"; + }; }; config = lib.mkIf cfg.enable { systemd.services.irccat = { inherit description; - preStart = "${configMerge}"; - script = "${config.depot.third_party.irccat}/bin/irccat"; wantedBy = [ "multi-user.target" ]; serviceConfig = { + ExecStart = "${mergeAndLaunch}"; DynamicUser = true; StateDirectory = "irccat"; WorkingDirectory = "/var/lib/irccat"; + LoadCredential = "secrets:${cfg.secretsFile}"; Restart = "always"; }; }; diff --git a/ops/modules/josh.nix b/ops/modules/josh.nix new file mode 100644 index 0000000000..4591ebf0f0 --- /dev/null +++ b/ops/modules/josh.nix @@ -0,0 +1,33 @@ +# Configures the public josh instance for serving the depot. +{ config, depot, lib, pkgs, ... }: + +let + cfg = config.services.depot.josh; +in +{ + options.services.depot.josh = with lib; { + enable = mkEnableOption "Enable josh for serving the depot"; + + port = mkOption { + description = "Port on which josh should listen"; + type = types.int; + default = 5674; + }; + }; + + config = lib.mkIf cfg.enable { + # Run josh for the depot. + systemd.services.josh = { + description = "josh - partial cloning of monorepos"; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.git pkgs.bash ]; + + serviceConfig = { + DynamicUser = true; + StateDirectory = "josh"; + Restart = "always"; + ExecStart = "${depot.third_party.josh}/bin/josh-proxy --no-background --local /var/lib/josh --port ${toString cfg.port} --remote https://cl.tvl.fyi/ --require-auth"; + }; + }; + }; +} diff --git a/ops/modules/journaldriver.nix b/ops/modules/journaldriver.nix new file mode 100644 index 0000000000..0d6b0bcc7f --- /dev/null +++ b/ops/modules/journaldriver.nix @@ -0,0 +1,26 @@ +# Configures journaldriver to forward to the tvl-fyi GCP project from +# TVL machines. +{ config, depot, lib, pkgs, ... }: + +{ + imports = [ + (depot.third_party.agenix.src + "/modules/age.nix") + ]; + + age.secrets.journaldriver.file = depot.ops.secrets."journaldriver.age"; + + services.journaldriver = { + enable = true; + googleCloudProject = "tvl-fyi"; + logStream = config.networking.hostName; + }; + + # Override the systemd service defined in the nixpkgs module to use + # the credentials provided by agenix. + systemd.services.journaldriver = { + serviceConfig = { + LoadCredential = "journaldriver.json:/run/agenix/journaldriver"; + ExecStart = lib.mkForce "${pkgs.coreutils}/bin/env GOOGLE_APPLICATION_CREDENTIALS=\"\${CREDENTIALS_DIRECTORY}/journaldriver.json\" ${depot.ops.journaldriver}/bin/journaldriver"; + }; + }; +} diff --git a/ops/modules/known-hosts.nix b/ops/modules/known-hosts.nix new file mode 100644 index 0000000000..9ea689178e --- /dev/null +++ b/ops/modules/known-hosts.nix @@ -0,0 +1,21 @@ +# Configure public keys for SSH hosts known to TVL. +{ ... }: + +{ + programs.ssh.knownHosts = { + whitby = { + hostNames = [ "whitby.tvl.fyi" "whitby.tvl.su" ]; + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I"; + }; + + sanduny = { + hostNames = [ "sanduny.tvl.su" ]; + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOag0XhylaTVhmT6HB8EN2Fv5Ymrc4ZfypOXONUkykTX"; + }; + + github = { + hostNames = [ "github.com" ]; + publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"; + }; + }; +} diff --git a/ops/modules/livegrep.nix b/ops/modules/livegrep.nix new file mode 100644 index 0000000000..e25a301829 --- /dev/null +++ b/ops/modules/livegrep.nix @@ -0,0 +1,106 @@ +# Configures a code search instance using Livegrep. +# +# We do not currently build Livegrep in Nix, because it's a complex, +# multi-language Bazel build and doesn't play nicely with Nix. +{ config, lib, pkgs, ... }: + +let + cfg = config.services.depot.livegrep; + + livegrepConfig = { + name = "livegrep"; + + fs_paths = [{ + name = "depot"; + path = "/depot"; + metadata.url_pattern = "https://code.tvl.fyi/tree/{path}?id={version}#n{lno}"; + }]; + + repositories = [{ + name = "depot"; + path = "/depot"; + revisions = [ "HEAD" ]; + + metadata = { + url_pattern = "https://code.tvl.fyi/tree/{path}?id={version}#n{lno}"; + remote = "https://cl.tvl.fyi/depot.git"; + }; + }]; + }; + + configFile = pkgs.writeText "livegrep-config.json" (builtins.toJSON livegrepConfig); + + # latest as of 2024-02-17 + image = "ghcr.io/livegrep/livegrep/base:033fa0e93c"; +in +{ + options.services.depot.livegrep = with lib; { + enable = mkEnableOption "Run livegrep code search for depot"; + + port = mkOption { + description = "Port on which livegrep web UI should listen"; + type = types.int; + default = 5477; # lgrp + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers.livegrep-codesearch = { + inherit image; + extraOptions = [ "--net=host" ]; + + volumes = [ + "${configFile}:/etc/livegrep-config.json:ro" + "/var/lib/gerrit/git/depot.git:/depot:ro" + ]; + + entrypoint = "/livegrep/bin/codesearch"; + cmd = [ + "-grpc" + "0.0.0.0:5427" # lgcs + "-reload_rpc" + "-revparse" + "/etc/livegrep-config.json" + ]; + }; + + virtualisation.oci-containers.containers.livegrep-frontend = { + inherit image; + dependsOn = [ "livegrep-codesearch" ]; + extraOptions = [ "--net=host" ]; + + entrypoint = "/livegrep/bin/livegrep"; + cmd = [ + "-listen" + "0.0.0.0:${toString cfg.port}" + "-reload" + "-connect" + "localhost:5427" + "-docroot" + "/livegrep/web" + # TODO(tazjin): docroot with styles etc. + ]; + }; + + systemd.services.livegrep-reindex = { + script = "${pkgs.docker}/bin/docker exec livegrep-codesearch /livegrep/bin/livegrep-reload localhost:5427"; + serviceConfig.Type = "oneshot"; + }; + + systemd.paths.livegrep-reindex = { + description = "Executes a livegrep reindex if depot refs change"; + wantedBy = [ "multi-user.target" ]; + + pathConfig = { + PathChanged = [ + "/var/lib/gerrit/git/depot.git/packed-refs" + "/var/lib/gerrit/git/depot.git/refs" + ]; + }; + }; + }; +} + + +# sudo docker exec -ti livegrep /livegrep/bin/codesearch -reload_rpc -revparse /var/lib/livegrep/config.jsno +# sudo docker run -d --ip 172.17.0.3 --name livegrep -v /var/lib/livegrep:/varlib/livegrep -v /var/lib/gerrit/git/depot.git:/depot:ro -v /home/tazjin/livegrep-web:/livegrep/web:ro ghcr.io/livegrep/livegrep/base /livegrep/bin/livegrep -listen 0.0.0.0:8910 -reload -docroot /livegrep/webbsudo docker run -d --ip 172.17.0.3 --name livegrep -v /var/lib/livegrep:/varlib/livegrep -v /var/lib/gerrit/git/depot.git:/depot:ro -v /home/tazjin/livegrep-web:/livegrep/web:ro ghcr.io/livegrep/livegrep/base /livegrep/bin/livegrep -listen 0.0.0.0:8910 -reload -docroot /livegrep/webb diff --git a/ops/modules/monorepo-gerrit.nix b/ops/modules/monorepo-gerrit.nix new file mode 100644 index 0000000000..b335fe61d5 --- /dev/null +++ b/ops/modules/monorepo-gerrit.nix @@ -0,0 +1,174 @@ +# Gerrit configuration for the TVL monorepo +{ depot, pkgs, config, lib, ... }: + +let + cfg = config.services.gerrit; + + besadiiWithConfig = name: pkgs.writeShellScript "besadii-whitby" '' + export BESADII_CONFIG=/run/agenix/gerrit-besadii-config + exec -a ${name} ${depot.ops.besadii}/bin/besadii "$@" + ''; + + gerritHooks = pkgs.runCommand "gerrit-hooks" { } '' + mkdir -p $out + ln -s ${besadiiWithConfig "change-merged"} $out/change-merged + ln -s ${besadiiWithConfig "patchset-created"} $out/patchset-created + ''; +in +{ + services.gerrit = { + enable = true; + listenAddress = "[::]:4778"; # 4778 - grrt + serverId = "4fdfa107-4df9-4596-8e0a-1d2bbdd96e36"; + + builtinPlugins = [ + "download-commands" + "hooks" + "replication" + ]; + + plugins = with depot.third_party.gerrit_plugins; [ + code-owners + oauth + depot.ops.gerrit-tvl + ]; + + package = depot.third_party.gerrit; + + jvmHeapLimit = "4g"; + + # In some NixOS channel bump, the default version of OpenJDK has + # changed to one that is incompatible with our current version of + # Gerrit. + # + # TODO(tazjin): Update Gerrit and remove this when possible. + jvmPackage = pkgs.openjdk17_headless; + + settings = { + core.packedGitLimit = "100m"; + log.jsonLogging = true; + log.textLogging = false; + sshd.advertisedAddress = "code.tvl.fyi:29418"; + hooks.path = "${gerritHooks}"; + cache.web_sessions.maxAge = "3 months"; + plugins.allowRemoteAdmin = false; + change.enableAttentionSet = true; + change.enableAssignee = false; + + # Configures gerrit for being reverse-proxied by nginx as per + # https://gerrit-review.googlesource.com/Documentation/config-reverseproxy.html + gerrit = { + canonicalWebUrl = "https://cl.tvl.fyi"; + docUrl = "/Documentation"; + }; + + httpd.listenUrl = "proxy-https://${cfg.listenAddress}"; + + download.command = [ + "checkout" + "cherry_pick" + "format_patch" + "pull" + ]; + + # Configure for cgit. + gitweb = { + type = "custom"; + url = "https://code.tvl.fyi"; + project = "/"; + revision = "/commit/?id=\${commit}"; + branch = "/log/?h=\${branch}"; + tag = "/tag/?h=\${tag}"; + roottree = "/tree/?h=\${commit}"; + file = "/tree/\${file}?h=\${commit}"; + filehistory = "/log/\${file}?h=\${branch}"; + linkname = "cgit"; + }; + + # Auto-link panettone bug links + commentlink.panettone = { + match = "b/(\\d+)"; + link = "https://b.tvl.fyi/issues/$1"; + }; + + # Auto-link other CLs + commentlink.gerrit = { + match = "cl/(\\d+)"; + link = "https://cl.tvl.fyi/$1"; + }; + + # Configures integration with Keycloak, which then integrates with a + # variety of backends. + auth.type = "OAUTH"; + plugin.gerrit-oauth-provider-keycloak-oauth = { + root-url = "https://auth.tvl.fyi/auth"; + realm = "TVL"; + client-id = "gerrit"; + # client-secret is set in /var/lib/gerrit/etc/secure.config. + }; + + plugin.code-owners = { + # A Code-Review +2 vote is required from a code owner. + requiredApproval = "Code-Review+2"; + # The OWNERS check can be overriden using an Owners-Override vote. + overrideApproval = "Owners-Override+1"; + # People implicitly approve their own changes automatically. + enableImplicitApprovals = "TRUE"; + }; + + # Allow users to add additional email addresses to their accounts. + oauth.allowRegisterNewEmail = true; + + # Use Gerrit's built-in HTTP passwords, rather than trying to use the + # password against the backing OAuth provider. + auth.gitBasicAuthPolicy = "HTTP"; + + # Email sending (emails are relayed via the tazj.in domain's + # GSuite currently). + # + # Note that sendemail.smtpPass is stored in + # $site_path/etc/secure.config and is *not* controlled by Nix. + # + # Receiving email is not currently supported. + sendemail = { + enable = true; + html = false; + connectTimeout = "10sec"; + from = "TVL Code Review <tvlbot@tazj.in>"; + includeDiff = true; + smtpEncryption = "none"; + smtpServer = "localhost"; + smtpServerPort = 2525; + }; + }; + + # Replication of the depot repository to secondary machines, for + # serving cgit/josh. + replicationSettings = { + gerrit.replicateOnStartup = true; + + remote.sanduny = { + url = "depot@sanduny.tvl.su:/var/lib/depot"; + projects = "depot"; + }; + }; + }; + + systemd.services.gerrit = { + serviceConfig = { + # There seems to be no easy way to get `DynamicUser` to play + # well with other services (e.g. by using SupplementaryGroups, + # which seem to have no effect) so we force the DynamicUser + # setting for the Gerrit service to be disabled and reuse the + # existing 'git' user. + DynamicUser = lib.mkForce false; + User = "git"; + Group = "git"; + }; + }; + + services.depot.restic = { + paths = [ "/var/lib/gerrit" ]; + exclude = [ "/var/lib/gerrit/tmp" ]; + }; +} diff --git a/ops/modules/nixery.nix b/ops/modules/nixery.nix new file mode 100644 index 0000000000..29da46cc1d --- /dev/null +++ b/ops/modules/nixery.nix @@ -0,0 +1,44 @@ +# NixOS module to run Nixery, currently with local-storage as the +# backend for storing/serving image layers. +{ depot, config, lib, pkgs, ... }: + +let + cfg = config.services.depot.nixery; + description = "Nixery - container images on-demand"; + nixpkgsSrc = depot.third_party.sources.nixpkgs-stable; + storagePath = "/var/lib/nixery/${nixpkgsSrc.rev}"; +in +{ + options.services.depot.nixery = { + enable = lib.mkEnableOption description; + + port = lib.mkOption { + type = lib.types.int; + default = 45243; # "image" + description = "Port on which Nixery should listen"; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.nixery = { + inherit description; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + StateDirectory = "nixery"; + Restart = "always"; + ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${storagePath}"; + ExecStart = "${depot.tools.nixery.nixery}/bin/server"; + }; + + environment = { + PORT = toString cfg.port; + NIXERY_PKGS_PATH = nixpkgsSrc.outPath; + NIXERY_STORAGE_BACKEND = "filesystem"; + NIX_TIMEOUT = "60"; # seconds + STORAGE_PATH = storagePath; + }; + }; + }; +} diff --git a/ops/modules/open_eid.nix b/ops/modules/open_eid.nix new file mode 100644 index 0000000000..fa577f0f57 --- /dev/null +++ b/ops/modules/open_eid.nix @@ -0,0 +1,54 @@ +# NixOS module to configure the Estonian e-ID software. +{ pkgs, ... }: + +{ + services.pcscd.enable = true; + + # Tell p11-kit to load/proxy opensc-pkcs11.so, providing all available slots + # (PIN1 for authentication/decryption, PIN2 for signing). + environment.etc."pkcs11/modules/opensc-pkcs11".text = '' + module: ${pkgs.opensc}/lib/opensc-pkcs11.so + ''; + + # Configure Firefox (in case users set `programs.firefox.enable = true;`) + programs.firefox = { + # Allow a possibly installed "Web eID" extension to do native messaging with + # the "web-eid-app" native component. + # Users not using `programs.firefox.enable` can override their firefox + # derivation, by setting `extraNativeMessagingHosts = [ pkgs.web-eid-app ]`. + nativeMessagingHosts.packages = [ pkgs.web-eid-app ]; + # Configure Firefox to load smartcards via p11kit-proxy. + # Users not using `programs.firefox.enable` can override their firefox + # derivation, by setting + # `extraPolicies.SecurityDevices.p11-kit-proxy "${pkgs.p11-kit}/lib/p11-kit-proxy.so"`. + policies.SecurityDevices.p11-kit-proxy = "${pkgs.p11-kit}/lib/p11-kit-proxy.so"; + }; + + # Chromium users need a symlink to their (slightly different) .json file + # in the native messaging hosts' manifest file location. + environment.etc."chromium/native-messaging-hosts/eu.webeid.json".source = "${pkgs.web-eid-app}/share/web-eid/eu.webeid.json"; + environment.etc."opt/chrome/native-messaging-hosts/eu.webeid.json".source = "${pkgs.web-eid-app}/share/web-eid/eu.webeid.json"; + + environment.systemPackages = with pkgs; [ + libdigidocpp.bin # provides digidoc-tool(1) + qdigidoc + + # Wrapper script to tell to Chrome/Chromium to use p11-kit-proxy to load + # security devices, so they can be used for TLS client auth. + # Each user needs to run this themselves, it does not work on a system level + # due to a bug in Chromium: + # + # https://bugs.chromium.org/p/chromium/issues/detail?id=16387 + # + # Firefox users can just set + # extraPolicies.SecurityDevices.p11-kit-proxy "${pkgs.p11-kit}/lib/p11-kit-proxy.so"; + # when overriding the firefox derivation. + (pkgs.writeShellScriptBin "setup-browser-eid" '' + NSSDB="''${HOME}/.pki/nssdb" + mkdir -p ''${NSSDB} + + ${pkgs.nssTools}/bin/modutil -force -dbdir sql:$NSSDB -add p11-kit-proxy \ + -libfile ${pkgs.p11-kit}/lib/p11-kit-proxy.so + '') + ]; +} diff --git a/ops/modules/owothia.nix b/ops/modules/owothia.nix new file mode 100644 index 0000000000..b9746c1720 --- /dev/null +++ b/ops/modules/owothia.nix @@ -0,0 +1,68 @@ +# Run the owothia IRC bot. +{ depot, config, lib, pkgs, ... }: + +let + cfg = config.services.depot.owothia; + description = "owothia - i'm a service owo"; +in +{ + options.services.depot.owothia = { + enable = lib.mkEnableOption description; + + secretsFile = lib.mkOption { + type = lib.types.str; + description = "File path from which systemd should read secrets"; + default = config.age.secretsDir + "/owothia"; + }; + + owoChance = lib.mkOption { + type = lib.types.int; + description = "How likely is owo?"; + default = 200; + }; + + ircServer = lib.mkOption { + type = lib.types.str; + description = "IRC server hostname"; + }; + + ircPort = lib.mkOption { + type = lib.types.int; + description = "IRC server port"; + }; + + ircIdent = lib.mkOption { + type = lib.types.str; + description = "IRC username"; + default = "owothia"; + }; + + ircChannels = lib.mkOption { + type = with lib.types; listOf str; + description = "IRC channels to join"; + default = [ "#tvl" ]; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.owothia = { + inherit description; + script = "${depot.fun.owothia}/bin/owothia"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + Restart = "always"; + EnvironmentFile = cfg.secretsFile; + }; + + environment = { + OWO_CHANCE = toString cfg.owoChance; + IRC_SERVER = cfg.ircServer; + IRC_PORT = toString cfg.ircPort; + IRC_IDENT = cfg.ircIdent; + IRC_CHANNELS = builtins.toJSON cfg.ircChannels; + }; + }; + }; +} diff --git a/ops/nixos/panettone.nix b/ops/modules/panettone.nix index 5082674357..e23dd028ab 100644 --- a/ops/nixos/panettone.nix +++ b/ops/modules/panettone.nix @@ -1,9 +1,9 @@ -{ config, lib, pkgs, ... }: +{ depot, config, lib, pkgs, ... }: let cfg = config.services.depot.panettone; - depot = config.depot; -in { +in +{ options.services.depot.panettone = with lib; { enable = mkEnableOption "Panettone issue tracker"; @@ -37,6 +37,7 @@ in { by systemd's EnvironmentFile ''; type = types.str; + default = config.age.secretsDir + "/panettone"; }; irccatHost = mkOption { @@ -62,23 +63,26 @@ in { assertion = cfg.dbHost != "localhost" || config.services.postgresql.enable; message = "Panettone requires a postgresql database"; - } { - assertion = - cfg.dbHost != "localhost" || config.services.postgresql.enableTCPIP; - message = "Panettone can only connect to the postgresql database over TCP"; - } { - assertion = - cfg.dbHost != "localhost" || (lib.any - (user: user.name == cfg.dbUser) - config.services.postgresql.ensureUsers); - message = "Panettone requires a database user"; - } { - assertion = - cfg.dbHost != "localhost" || (lib.any - (db: db == cfg.dbName) - config.services.postgresql.ensureDatabases); - message = "Panettone requires a database"; - }]; + } + { + assertion = + cfg.dbHost != "localhost" || config.services.postgresql.enableTCPIP; + message = "Panettone can only connect to the postgresql database over TCP"; + } + { + assertion = + cfg.dbHost != "localhost" || (lib.any + (user: user.name == cfg.dbUser) + config.services.postgresql.ensureUsers); + message = "Panettone requires a database user"; + } + { + assertion = + cfg.dbHost != "localhost" || (lib.any + (db: db == cfg.dbName) + config.services.postgresql.ensureDatabases); + message = "Panettone requires a database"; + }]; systemd.services.panettone = { wantedBy = [ "multi-user.target" ]; @@ -100,5 +104,16 @@ in { ISSUECHANNEL = cfg.irccatChannel; }; }; + + systemd.services.panettone-fixer = { + description = "Restart panettone regularly to work around b/225"; + wantedBy = [ "multi-user.target" ]; + script = "${pkgs.systemd}/bin/systemctl restart panettone"; + serviceConfig.Type = "oneshot"; + + # We don't exactly know how frequently this occurs, but + # _probably_ not more than hourly. + startAt = "hourly"; + }; }; } diff --git a/ops/nixos/paroxysm.nix b/ops/modules/paroxysm.nix index 24c5377a57..070e7623db 100644 --- a/ops/nixos/paroxysm.nix +++ b/ops/modules/paroxysm.nix @@ -1,15 +1,16 @@ -{ config, lib, pkgs, ... }: +{ depot, config, lib, pkgs, ... }: let cfg = config.services.depot.paroxysm; description = "TVL's majestic IRC bot"; -in { +in +{ options.services.depot.paroxysm.enable = lib.mkEnableOption description; config = lib.mkIf cfg.enable { systemd.services.paroxysm = { inherit description; - script = "${config.depot.fun.paroxysm}/bin/paroxysm"; + script = "${depot.fun.paroxysm}/bin/paroxysm"; wantedBy = [ "multi-user.target" ]; environment = { diff --git a/ops/modules/prometheus-fail2ban-exporter.nix b/ops/modules/prometheus-fail2ban-exporter.nix new file mode 100644 index 0000000000..349364f9b7 --- /dev/null +++ b/ops/modules/prometheus-fail2ban-exporter.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, depot, ... }: + +let + cfg = config.services.prometheus-fail2ban-exporter; +in + +{ + options.services.prometheus-fail2ban-exporter = with lib; { + enable = mkEnableOption "Prometheus Fail2ban Exporter"; + + interval = mkOption { + description = "Systemd calendar expression for how often to run the interval"; + type = types.string; + default = "minutely"; + example = "hourly"; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services."prometheus-fail2ban-exporter" = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "fail2ban.service" ]; + serviceConfig = { + User = "root"; + Type = "oneshot"; + ExecStart = pkgs.writeShellScript "prometheus-fail2ban-exporter" '' + set -eo pipefail + mkdir -p /var/lib/prometheus/node-exporter + exec prometheus-fail2ban-exporter + ''; + }; + + path = [ + pkgs.fail2ban + depot.third_party.prometheus-fail2ban-exporter + ]; + }; + + systemd.timers."prometheus-fail2ban-exporter" = { + wantedBy = [ "multi-user.target" ]; + timerConfig.OnCalendar = cfg.interval; + }; + + services.prometheus.exporters.node = { + enabledCollectors = [ "textfile" ]; + + extraFlags = [ + "--collector.textfile.directory=/var/lib/prometheus/node-exporter" + ]; + }; + }; +} diff --git a/ops/nixos/quassel.nix b/ops/modules/quassel.nix index df26a39455..6acb0615f4 100644 --- a/ops/nixos/quassel.nix +++ b/ops/modules/quassel.nix @@ -8,7 +8,8 @@ let enableDaemon = true; withKDE = false; }; -in { +in +{ options.services.depot.quassel = with lib; { enable = mkEnableOption "Quassel IRC daemon"; @@ -42,6 +43,8 @@ in { }; config = with lib; mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ cfg.port ]; + systemd.services.quassel = { description = "Quassel IRC daemon"; wantedBy = [ "multi-user.target" ]; @@ -52,7 +55,7 @@ in { "--port=${toString cfg.port}" "--configdir=/var/lib/quassel" "--require-ssl" - "--ssl-cert=/var/lib/acme/${cfg.acmeHost}/full.pem" + "--ssl-cert=$CREDENTIALS_DIRECTORY/quassel.pem" "--loglevel=${cfg.logLevel}" ]; @@ -61,16 +64,20 @@ in { User = "quassel"; Group = "quassel"; StateDirectory = "quassel"; + + # Avoid trouble with the ACME file permissions by using the + # systemd credentials feature. + LoadCredential = "quassel.pem:/var/lib/acme/${cfg.acmeHost}/full.pem"; }; }; users = { users.quassel = { - isNormalUser = false; + isSystemUser = true; group = "quassel"; }; - groups.quassel = {}; + groups.quassel = { }; }; }; } diff --git a/ops/modules/restic.nix b/ops/modules/restic.nix new file mode 100644 index 0000000000..8695396035 --- /dev/null +++ b/ops/modules/restic.nix @@ -0,0 +1,62 @@ +# Configure restic backups to S3-compatible storage, in our case +# GleSYS object storage. +# +# Conventions: +# - restic's cache lives in /var/backup/restic/cache +# - repository password lives in /var/backup/restic/secret +# - object storage credentials in /var/backup/restic/glesys-key +{ config, lib, pkgs, ... }: + +let + cfg = config.services.depot.restic; + description = "Restic backups to GleSYS"; + mkStringOption = default: lib.mkOption { + inherit default; + type = lib.types.str; + }; +in +{ + options.services.depot.restic = { + enable = lib.mkEnableOption description; + bucketEndpoint = mkStringOption "objects.dc-sto1.glesys.net"; + bucketName = mkStringOption "aged-resonance"; + bucketCredentials = mkStringOption "/var/backup/restic/glesys-key"; + repository = mkStringOption config.networking.hostName; + interval = mkStringOption "hourly"; + + paths = with lib; mkOption { + description = "Directories that should be backed up"; + type = types.listOf types.str; + }; + + exclude = with lib; mkOption { + description = "Files that should be excluded from backups"; + type = types.listOf types.str; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.restic = { + description = "Backups to GleSYS"; + + script = "${pkgs.restic}/bin/restic backup ${lib.concatStringsSep " " cfg.paths}"; + + environment = { + RESTIC_REPOSITORY = "s3:${cfg.bucketEndpoint}/${cfg.bucketName}/${cfg.repository}"; + AWS_SHARED_CREDENTIALS_FILE = cfg.bucketCredentials; + RESTIC_PASSWORD_FILE = "/var/backup/restic/secret"; + RESTIC_CACHE_DIR = "/var/backup/restic/cache"; + + RESTIC_EXCLUDE_FILE = + builtins.toFile "exclude-files" (lib.concatStringsSep "\n" cfg.exclude); + }; + }; + + systemd.timers.restic = { + wantedBy = [ "multi-user.target" ]; + timerConfig.OnCalendar = cfg.interval; + }; + + environment.systemPackages = [ pkgs.restic ]; + }; +} diff --git a/ops/nixos/smtprelay.nix b/ops/modules/smtprelay.nix index 044902b15a..f6ce262175 100644 --- a/ops/nixos/smtprelay.nix +++ b/ops/modules/smtprelay.nix @@ -1,5 +1,5 @@ # NixOS module for configuring the simple SMTP relay. -{ pkgs, config, lib, ... }: +{ depot, pkgs, config, lib, ... }: let inherit (builtins) attrValues mapAttrs; @@ -9,44 +9,52 @@ let mkIf mkOption types -; + ; cfg = config.services.depot.smtprelay; description = "Simple SMTP relay"; - # Configuration values that are always overridden. In particular, - # `config` is specified to always load $StateDirectory/secure.config - # (so that passwords can be loaded from there) and logging is pinned - # to stdout for journald compatibility. + # Configuration values that are always overridden. + # + # - logging is pinned to stdout for journald compatibility + # - secret config is loaded through systemd's credential loading facility overrideArgs = { logfile = ""; - config = "/var/lib/smtprelay/secure.config"; + config = "$CREDENTIALS_DIRECTORY/secrets"; }; # Creates the command line argument string for the service. prepareArgs = args: concatStringsSep " " - (attrValues (mapAttrs (key: value: "-${key} '${toString value}'") - (args // overrideArgs))); -in { + (attrValues (mapAttrs (key: value: "-${key} \"${toString value}\"") + (args // overrideArgs))); +in +{ options.services.depot.smtprelay = { enable = mkEnableOption description; + args = mkOption { type = types.attrsOf types.str; description = "Key value pairs for command line arguments"; }; + + secretsFile = mkOption { + type = types.str; + default = config.age.secretsDir + "/smtprelay"; + }; }; config = mkIf cfg.enable { systemd.services.smtprelay = { inherit description; - script = "${config.depot.third_party.smtprelay}/bin/smtprelay ${prepareArgs cfg.args}"; + script = "${depot.third_party.smtprelay}/bin/smtprelay ${prepareArgs cfg.args}"; wantedBy = [ "multi-user.target" ]; serviceConfig = { Restart = "always"; StateDirectory = "smtprelay"; DynamicUser = true; + LoadCredential = "secrets:${cfg.secretsFile}"; }; }; }; diff --git a/ops/nixos/sourcegraph.nix b/ops/modules/sourcegraph.nix index 43dc275ee1..cbf836ab64 100644 --- a/ops/nixos/sourcegraph.nix +++ b/ops/modules/sourcegraph.nix @@ -1,11 +1,11 @@ # Run sourcegraph, including its entire machinery, in a container. # Running it outside of a container is a futile endeavour for now. -{ config, pkgs, lib, ... }: +{ depot, config, pkgs, lib, ... }: let cfg = config.services.depot.sourcegraph; - depot = config.depot; -in { +in +{ options.services.depot.sourcegraph = with lib; { enable = mkEnableOption "SourceGraph code search engine"; @@ -35,7 +35,7 @@ in { }; virtualisation.oci-containers.containers.sourcegraph = { - image = "sourcegraph/server:3.18.0"; + image = "sourcegraph/server:3.40.0"; ports = [ "127.0.0.1:${toString cfg.port}:7080" @@ -46,7 +46,15 @@ in { "/var/lib/sourcegraph/data:/var/opt/sourcegraph" ]; - environment.SRC_SYNTECT_SERVER = "http://172.17.0.1:${toString cfg.cheddarPort}"; + # TODO(tazjin): Figure out what changed in the protocol. + # environment.SRC_SYNTECT_SERVER = "http://172.17.0.1:${toString cfg.cheddarPort}"; + + # Sourcegraph needs a higher nofile limit, it logs warnings + # otherwise (unclear whether it actually affects the service). + extraOptions = [ + "--ulimit" + "nofile=10000:10000" + ]; }; }; } diff --git a/ops/modules/tvl-buildkite.nix b/ops/modules/tvl-buildkite.nix new file mode 100644 index 0000000000..3c6d88404f --- /dev/null +++ b/ops/modules/tvl-buildkite.nix @@ -0,0 +1,80 @@ +# Configuration for the TVL buildkite agents. +{ config, depot, pkgs, lib, ... }: + +let + cfg = config.services.depot.buildkite; + agents = lib.range 1 cfg.agentCount; + description = "Buildkite agents for TVL"; + + besadiiWithConfig = name: pkgs.writeShellScript "besadii-whitby" '' + export BESADII_CONFIG=/run/agenix/buildkite-besadii-config + exec -a ${name} ${depot.ops.besadii}/bin/besadii "$@" + ''; + + # All Buildkite hooks are actually besadii, but it's being invoked + # with different names. + buildkiteHooks = pkgs.runCommand "buildkite-hooks" { } '' + mkdir -p $out/bin + ln -s ${besadiiWithConfig "post-command"} $out/bin/post-command + ''; + + credentialHelper = pkgs.writeShellScriptBin "git-credential-gerrit-creds" '' + echo 'username=buildkite' + echo "password=$(jq -r '.gerritPassword' /run/agenix/buildkite-besadii-config)" + ''; +in +{ + options.services.depot.buildkite = { + enable = lib.mkEnableOption description; + agentCount = lib.mkOption { + type = lib.types.int; + description = "Number of Buildkite agents to launch"; + }; + }; + + config = lib.mkIf cfg.enable { + # Run the Buildkite agents using the default upstream module. + services.buildkite-agents = builtins.listToAttrs (map + (n: rec { + name = "whitby-${toString n}"; + value = { + inherit name; + enable = true; + tokenPath = config.age.secretsDir + "/buildkite-agent-token"; + privateSshKeyPath = config.age.secretsDir + "/buildkite-private-key"; + hooks.post-command = "${buildkiteHooks}/bin/post-command"; + hooks.environment = '' + export PATH=$PATH:/run/wrappers/bin + ''; + + runtimePackages = with pkgs; [ + bash + coreutils + credentialHelper + curl + git + gnutar + gzip + jq + nix + ]; + }; + }) + agents); + + # Set up a group for all Buildkite agent users + users = { + groups.buildkite-agents = { }; + users = builtins.listToAttrs (map + (n: rec { + name = "buildkite-agent-whitby-${toString n}"; + value = { + isSystemUser = true; + group = lib.mkForce "buildkite-agents"; + extraGroups = [ name "docker" ]; + }; + }) + agents); + }; + }; +} diff --git a/ops/modules/tvl-cache.nix b/ops/modules/tvl-cache.nix new file mode 100644 index 0000000000..683818d103 --- /dev/null +++ b/ops/modules/tvl-cache.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: + +{ + options = { + tvl.cache.enable = lib.mkEnableOption "the TVL binary cache"; + }; + + config = lib.mkIf config.tvl.cache.enable { + nix.settings = { + trusted-public-keys = [ + "cache.tvl.su:kjc6KOMupXc1vHVufJUoDUYeLzbwSr9abcAKdn/U1Jk=" + ]; + + substituters = [ + "https://cache.tvl.su" + ]; + }; + }; +} diff --git a/ops/modules/tvl-headscale.nix b/ops/modules/tvl-headscale.nix new file mode 100644 index 0000000000..a07021c788 --- /dev/null +++ b/ops/modules/tvl-headscale.nix @@ -0,0 +1,62 @@ +# Configuration for the coordination server for net.tvl.fyi, a +# tailscale network run using headscale. +# +# All TVL members can join this network, which provides several exit +# nodes through which traffic can be routed. +# +# The coordination server is currently run on sanduny.tvl.su. It is +# managed manually, ping somebody with access ... for access. +# +# Servers should join using approximately this command: +# tailscale up --login-server https://net.tvl.fyi --accept-dns=false --advertise-exit-node +# +# Clients should join using approximately this command: +# tailscale up --login-server https://net.tvl.fyi --accept-dns=false +{ config, pkgs, ... }: + +{ + # TODO(tazjin): run embedded DERP server + services.headscale = { + enable = true; + port = 4725; # hscl + + settings = { + server_url = "https://net.tvl.fyi"; + dns_config.nameservers = [ + "8.8.8.8" + "1.1.1.1" + "77.88.8.8" + ]; + + # TLS is handled by nginx + tls_cert_path = null; + tls_key_path = null; + }; + }; + + environment.systemPackages = [ pkgs.headscale ]; # admin CLI + + services.nginx.virtualHosts."net.tvl.fyi" = { + serverName = "net.tvl.fyi"; + enableACME = true; + forceSSL = true; + + # See https://github.com/juanfont/headscale/blob/v0.22.3/docs/reverse-proxy.md#nginx + extraConfig = '' + location / { + proxy_pass http://localhost:${toString config.services.headscale.port}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $server_name; + proxy_redirect http:// https://; + proxy_buffering off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + } + ''; + }; + +} diff --git a/ops/modules/tvl-slapd/default.nix b/ops/modules/tvl-slapd/default.nix new file mode 100644 index 0000000000..c08cd30f16 --- /dev/null +++ b/ops/modules/tvl-slapd/default.nix @@ -0,0 +1,81 @@ +# Configures an OpenLDAP instance for TVL +# +# TODO(tazjin): Configure ldaps:// +{ depot, lib, pkgs, ... }: + +with depot.nix.yants; + +let + user = struct { + username = string; + email = string; + password = string; + displayName = option string; + }; + + toLdif = defun [ user string ] (u: '' + dn: cn=${u.username},ou=users,dc=tvl,dc=fyi + objectClass: organizationalPerson + objectClass: inetOrgPerson + sn: ${u.username} + cn: ${u.username} + displayName: ${u.displayName or u.username} + mail: ${u.email} + userPassword: ${u.password} + ''); + + inherit (depot.ops) users; + +in +{ + services.openldap = { + enable = true; + + settings.children = { + "olcDatabase={1}mdb".attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/lib/openldap/db"; + olcSuffix = "dc=tvl,dc=fyi"; + olcAccess = "to * by * read"; + olcRootDN = "cn=admin,dc=tvl,dc=fyi"; + olcRootPW = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OfcgkOQ96VQ3aJj7NfA9vQ$oS6HQOkYl/bUYg4SejpltQYy7kvqx/RUxvoR4zo1vXU"; + }; + + "cn=module{0}".attrs = { + objectClass = "olcModuleList"; + olcModuleLoad = "argon2"; + }; + + "cn=schema".includes = + map (schema: "${pkgs.openldap}/etc/schema/${schema}.ldif") + [ "core" "cosine" "inetorgperson" "nis" ]; + }; + + # Contents are immutable at runtime, and adding user accounts etc. + # is done statically in the LDIF-formatted contents in this folder. + declarativeContents."dc=tvl,dc=fyi" = '' + dn: dc=tvl,dc=fyi + dc: tvl + o: TVL LDAP server + description: Root entry for tvl.fyi + objectClass: top + objectClass: dcObject + objectClass: organization + + dn: ou=users,dc=tvl,dc=fyi + ou: users + description: All users in TVL + objectClass: top + objectClass: organizationalUnit + + dn: ou=groups,dc=tvl,dc=fyi + ou: groups + description: All groups in TVL + objectClass: top + objectClass: organizationalUnit + + ${lib.concatStringsSep "\n" (map toLdif users)} + ''; + }; +} diff --git a/ops/modules/tvl-users.nix b/ops/modules/tvl-users.nix new file mode 100644 index 0000000000..ea83b435f4 --- /dev/null +++ b/ops/modules/tvl-users.nix @@ -0,0 +1,83 @@ +# Standard NixOS users for TVL machines, as well as configuration that +# should following along when they are added to a machine. +{ depot, pkgs, ... }: + +{ + users = { + users.tazjin = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + shell = pkgs.fish; + openssh.authorizedKeys.keys = depot.users.tazjin.keys.all; + }; + + users.lukegb = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + openssh.authorizedKeys.keys = depot.users.lukegb.keys.all; + }; + + users.aspen = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + openssh.authorizedKeys.keys = [ depot.users.aspen.keys.whitby ]; + }; + + users.edef = { + isNormalUser = true; + extraGroups = [ "git" ]; + openssh.authorizedKeys.keys = depot.users.edef.keys.all; + }; + + users.qyliss = { + isNormalUser = true; + description = "Alyssa Ross"; + extraGroups = [ "git" ]; + openssh.authorizedKeys.keys = depot.users.qyliss.keys.all; + }; + + users.eta = { + isNormalUser = true; + extraGroups = [ "git" ]; + openssh.authorizedKeys.keys = depot.users.eta.keys.whitby; + }; + + users.cynthia = { + isNormalUser = true; # I'm normal OwO :3 + extraGroups = [ "git" ]; + openssh.authorizedKeys.keys = depot.users.cynthia.keys.all; + }; + + users.firefly = { + isNormalUser = true; + extraGroups = [ "git" ]; + openssh.authorizedKeys.keys = depot.users.firefly.keys.whitby; + }; + + users.sterni = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + openssh.authorizedKeys.keys = depot.users.sterni.keys.all; + }; + + users.flokli = { + isNormalUser = true; + extraGroups = [ "git" "wheel" ]; + openssh.authorizedKeys.keys = depot.users.flokli.keys.all; + }; + }; + + programs.fish.enable = true; + + environment.systemPackages = with pkgs; [ + alacritty.terminfo + foot.terminfo + rxvt-unicode-unwrapped.terminfo + kitty.terminfo + ]; + + security.sudo.extraRules = [{ + groups = [ "wheel" ]; + commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }]; + }]; +} diff --git a/ops/modules/www/atward.tvl.fyi.nix b/ops/modules/www/atward.tvl.fyi.nix new file mode 100644 index 0000000000..6b3672dd75 --- /dev/null +++ b/ops/modules/www/atward.tvl.fyi.nix @@ -0,0 +1,33 @@ +# Serve atward, the query redirection ... thing. +{ config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + # Short link support (i.e. plain http://at) for users with a + # configured tvl.fyi/tvl.su search domain. + services.nginx.virtualHosts."at-shortlink" = { + serverName = "at"; + extraConfig = "return 302 https://atward.tvl.fyi$request_uri;"; + }; + + services.nginx.virtualHosts."atward" = { + serverName = "atward.tvl.fyi"; + enableACME = true; + forceSSL = true; + + serverAliases = [ + "atward.tvl.su" + "at.tvl.fyi" + "at.tvl.su" + ]; + + locations."/" = { + proxyPass = "http://localhost:${toString config.services.depot.atward.port}"; + }; + }; + }; +} diff --git a/ops/nixos/www/login.tvl.fyi.nix b/ops/modules/www/auth.tvl.fyi.nix index 05b7cee253..a068f02365 100644 --- a/ops/nixos/www/login.tvl.fyi.nix +++ b/ops/modules/www/auth.tvl.fyi.nix @@ -1,4 +1,4 @@ -{ ... }: +{ config, ... }: { imports = [ @@ -6,14 +6,18 @@ ]; config = { - services.nginx.virtualHosts."login.tvl.fyi" = { - serverName = "login.tvl.fyi"; + services.nginx.virtualHosts."auth.tvl.fyi" = { + serverName = "auth.tvl.fyi"; enableACME = true; forceSSL = true; extraConfig = '' + # increase buffer size for large headers + proxy_buffers 8 16k; + proxy_buffer_size 16k; + location / { - proxy_pass http://localhost:8443; + proxy_pass http://localhost:${toString config.services.keycloak.settings.http-port}; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $host; diff --git a/ops/modules/www/b.tvl.fyi.nix b/ops/modules/www/b.tvl.fyi.nix new file mode 100644 index 0000000000..45f6c6ed51 --- /dev/null +++ b/ops/modules/www/b.tvl.fyi.nix @@ -0,0 +1,32 @@ +{ config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."b-shortlink" = { + serverName = "b"; + extraConfig = "return 302 https://b.tvl.fyi$request_uri;"; + }; + + services.nginx.virtualHosts."b.tvl.fyi" = { + serverName = "b.tvl.fyi"; + serverAliases = [ "b.tvl.su" ]; + enableACME = true; + forceSSL = true; + + extraConfig = '' + # Forward short links to issues to the issue itself (b/32) + location ~ ^/(\d+)$ { + return 302 https://b.tvl.fyi/issues$request_uri; + } + + location / { + proxy_pass http://localhost:${toString config.services.depot.panettone.port}; + } + ''; + }; + }; +} diff --git a/ops/modules/www/base.nix b/ops/modules/www/base.nix new file mode 100644 index 0000000000..50fceff0fa --- /dev/null +++ b/ops/modules/www/base.nix @@ -0,0 +1,41 @@ +{ config, pkgs, ... }: + +{ + config = { + security.acme = { + acceptTerms = true; + defaults.email = "letsencrypt@tvl.su"; + }; + + services.nginx = { + enable = true; + enableReload = true; + + recommendedTlsSettings = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + commonHttpConfig = '' + log_format json_combined escape=json + '{' + '"remote_addr":"$remote_addr",' + '"method":"$request_method",' + '"host":"$host",' + '"uri":"$request_uri",' + '"status":$status,' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ''; + + appendHttpConfig = '' + add_header Permissions-Policy "interest-cohort=()"; + ''; + }; + }; +} diff --git a/ops/modules/www/cache.tvl.su.nix b/ops/modules/www/cache.tvl.su.nix new file mode 100644 index 0000000000..99bc008cd6 --- /dev/null +++ b/ops/modules/www/cache.tvl.su.nix @@ -0,0 +1,31 @@ +{ config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."cache.tvl.su" = { + serverName = "cache.tvl.su"; + serverAliases = [ "cache.tvl.fyi" ]; + enableACME = true; + forceSSL = true; + + extraConfig = '' + location = /cache-key.pub { + alias /run/agenix/nix-cache-pub; + } + + location = /nix-cache-info { + add_header Content-Type text/plain; + return 200 "StoreDir: /nix/store\nWantMassQuery: 1\nPriority: 50\n"; + } + + location / { + proxy_pass http://localhost:${toString config.services.nix-serve.port}; + } + ''; + }; + }; +} diff --git a/ops/nixos/www/cl.tvl.fyi.nix b/ops/modules/www/cl.tvl.fyi.nix index bcaab85f02..36422a6c4e 100644 --- a/ops/nixos/www/cl.tvl.fyi.nix +++ b/ops/modules/www/cl.tvl.fyi.nix @@ -6,8 +6,14 @@ ]; config = { + services.nginx.virtualHosts."cl-shortlink" = { + serverName = "cl"; + extraConfig = "return 302 https://cl.tvl.fyi$request_uri;"; + }; + services.nginx.virtualHosts.gerrit = { serverName = "cl.tvl.fyi"; + serverAliases = [ "cl.tvl.su" ]; enableACME = true; forceSSL = true; @@ -18,6 +24,10 @@ # The :443 suffix is a workaround for https://b.tvl.fyi/issues/88. proxy_set_header Host $host:443; } + + location = /robots.txt { + return 200 'User-agent: *\nAllow: /'; + } ''; }; }; diff --git a/ops/modules/www/code.tvl.fyi.nix b/ops/modules/www/code.tvl.fyi.nix new file mode 100644 index 0000000000..ee0211990d --- /dev/null +++ b/ops/modules/www/code.tvl.fyi.nix @@ -0,0 +1,78 @@ +{ depot, pkgs, config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts.cgit = { + serverName = "code.tvl.fyi"; + serverAliases = [ "code.tvl.su" ]; + enableACME = true; + forceSSL = true; + + extraConfig = '' + location = /go-get/tvix/build-go { + alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/build-go git https://code.tvl.fyi/depot.git:/tvix/build-go.git"></html>''}; + } + + location = /go-get/tvix/castore-go { + alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/castore-go git https://code.tvl.fyi/depot.git:/tvix/castore-go.git"></html>''}; + } + + location = /go-get/tvix/store-go { + alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/store-go git https://code.tvl.fyi/depot.git:/tvix/store-go.git"></html>''}; + } + + location = /go-get/tvix/nar-bridge { + alias ${pkgs.writeText "go-import-metadata.html" ''<html><meta name="go-import" content="code.tvl.fyi/tvix/nar-bridge git https://code.tvl.fyi/depot.git:/tvix/nar-bridge.git"></html>''}; + } + + location = /tvix/build-go { + if ($args ~* "/?go-get=1") { + return 302 /go-get/tvix/build-go; + } + } + + location = /tvix/castore-go { + if ($args ~* "/?go-get=1") { + return 302 /go-get/tvix/castore-go; + } + } + + location = /tvix/store-go { + if ($args ~* "/?go-get=1") { + return 302 /go-get/tvix/store-go; + } + } + + location = /tvix/nar-bridge { + if ($args ~* "/?go-get=1") { + return 302 /go-get/tvix/nar-bridge; + } + } + + # Git operations on depot.git hit josh + location /depot.git { + proxy_pass http://127.0.0.1:${toString config.services.depot.josh.port}; + } + + # Git clone operations on '/' should be redirected to josh now. + location = /info/refs { + return 302 https://code.tvl.fyi/depot.git/info/refs$is_args$args; + } + + # Static assets must always hit the root. + location ~ ^/(favicon\.ico|cgit\.(css|png))$ { + proxy_pass http://localhost:2448; + } + + # Everything else is forwarded to cgit for the web view + location / { + proxy_pass http://localhost:2448/cgit.cgi/depot/; + } + ''; + }; + }; +} diff --git a/ops/nixos/www/cs.tvl.fyi.nix b/ops/modules/www/cs.tvl.fyi.nix index ed2adcbf82..fac814baf0 100644 --- a/ops/nixos/www/cs.tvl.fyi.nix +++ b/ops/modules/www/cs.tvl.fyi.nix @@ -8,6 +8,7 @@ config = { services.nginx.virtualHosts."cs.tvl.fyi" = { serverName = "cs.tvl.fyi"; + serverAliases = [ "cs.tvl.su" ]; enableACME = true; forceSSL = true; diff --git a/ops/modules/www/deploys.tvl.fyi.nix b/ops/modules/www/deploys.tvl.fyi.nix new file mode 100644 index 0000000000..ffbe225b58 --- /dev/null +++ b/ops/modules/www/deploys.tvl.fyi.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + # Ensure the directory for deployment diffs exists. + systemd.tmpfiles.rules = [ + "d /var/html/deploys.tvl.fyi/diff 0755 nginx nginx -" + ]; + + services.nginx.virtualHosts."deploys.tvl.fyi" = { + enableACME = true; + forceSSL = true; + root = "/var/html/deploys.tvl.fyi"; + }; + + services.depot.restic.paths = [ "/var/html/deploys.tvl.fyi" ]; + }; +} diff --git a/ops/modules/www/grep.tvl.fyi.nix b/ops/modules/www/grep.tvl.fyi.nix new file mode 100644 index 0000000000..93ef5eabd2 --- /dev/null +++ b/ops/modules/www/grep.tvl.fyi.nix @@ -0,0 +1,19 @@ +# Experimental configuration for manually Livegrep. +{ config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."grep.tvl.fyi" = { + enableACME = true; + forceSSL = true; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString config.services.depot.livegrep.port}"; + }; + }; + }; +} diff --git a/ops/modules/www/inbox.tvl.su.nix b/ops/modules/www/inbox.tvl.su.nix new file mode 100644 index 0000000000..38db5d2a8e --- /dev/null +++ b/ops/modules/www/inbox.tvl.su.nix @@ -0,0 +1,31 @@ +{ config, depot, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."inbox.tvl.su" = { + enableACME = true; + forceSSL = true; + + extraConfig = '' + # nginx is incapable of serving a single file at /, hence this hack: + location = / { + index /landing-page; + } + + location = /landing-page { + types { } default_type "text/html; charset=utf-8"; + alias ${depot.web.inbox}; + } + + # rest of requests is proxied to public-inbox-httpd + location / { + proxy_pass http://localhost:${toString config.services.public-inbox.http.port}; + } + ''; + }; + }; +} diff --git a/ops/nixos/www/b.tvl.fyi.nix b/ops/modules/www/nixery.dev.nix index 3d8a4068aa..05dc88c66a 100644 --- a/ops/nixos/www/b.tvl.fyi.nix +++ b/ops/modules/www/nixery.dev.nix @@ -6,14 +6,14 @@ ]; config = { - services.nginx.virtualHosts."b.tvl.fyi" = { - serverName = "b.tvl.fyi"; + services.nginx.virtualHosts."nixery.dev" = { + serverName = "nixery.dev"; enableACME = true; forceSSL = true; extraConfig = '' location / { - proxy_pass http://localhost:${toString config.services.depot.panettone.port}; + proxy_pass http://localhost:${toString config.services.depot.nixery.port}; } ''; }; diff --git a/ops/modules/www/self-redirect.nix b/ops/modules/www/self-redirect.nix new file mode 100644 index 0000000000..5bf1627be9 --- /dev/null +++ b/ops/modules/www/self-redirect.nix @@ -0,0 +1,27 @@ +# Redirect the hostname of a machine to its configuration in a web +# browser. +# +# Works by convention, assuming that the machine has its configuration +# at //ops/machines/${hostname}. +{ config, ... }: + +let + host = "${config.networking.hostName}.${config.networking.domain}"; +in +{ + imports = [ + ./base.nix + ]; + + config.services.nginx.virtualHosts."${host}" = { + serverName = host; + addSSL = true; # SSL is not forced on these redirects + enableACME = true; + + extraConfig = '' + location = / { + return 302 https://at.tvl.fyi/?q=%2F%2Fops%2Fmachines%2F${config.networking.hostName}; + } + ''; + }; +} diff --git a/ops/modules/www/signup.tvl.fyi.nix b/ops/modules/www/signup.tvl.fyi.nix new file mode 100644 index 0000000000..1b193f99a9 --- /dev/null +++ b/ops/modules/www/signup.tvl.fyi.nix @@ -0,0 +1,19 @@ +{ depot, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."signup.tvl.fyi" = { + root = depot.web.pwcrypt; + enableACME = true; + forceSSL = true; + + extraConfig = '' + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + ''; + }; + }; +} diff --git a/ops/modules/www/static.tvl.fyi.nix b/ops/modules/www/static.tvl.fyi.nix new file mode 100644 index 0000000000..7312f78ecf --- /dev/null +++ b/ops/modules/www/static.tvl.fyi.nix @@ -0,0 +1,42 @@ +# Host the static assets at static.tvl.fyi +# +# All assets are served from $base/$drvhash/$file, but can also be +# included with `latest/` which will return a (non-permanent!) +# redirect to the real location. +# +# For all purposes within depot, using the drvhash of web.static is +# recommended. +{ depot, pkgs, ... }: + +let staticHash = depot.web.static.drvHash; +in { + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."static.tvl.fyi" = { + serverAliases = [ "static.tvl.su" ]; + enableACME = true; + forceSSL = true; + + extraConfig = '' + location = / { + add_header Content-Type text/plain; + return 200 "looking for tvl.fyi or tvl.su?"; + } + + location /latest { + rewrite ^/latest/(.*) /${staticHash}/$1 redirect; + } + + location /${staticHash}/ { + alias ${depot.web.static}/; + expires max; + add_header Access-Control-Allow-Origin "*"; + add_header Cache-Control "public"; + } + ''; + }; + }; +} diff --git a/ops/modules/www/status.tvl.su.nix b/ops/modules/www/status.tvl.su.nix new file mode 100644 index 0000000000..7079c60260 --- /dev/null +++ b/ops/modules/www/status.tvl.su.nix @@ -0,0 +1,25 @@ +{ config, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."status-fyi" = { + serverName = "status.tvl.fyi"; + enableACME = true; + extraConfig = "return 302 https://status.tvl.su$request_uri;"; + }; + + services.nginx.virtualHosts.grafana = { + serverName = "status.tvl.su"; + enableACME = true; + forceSSL = true; + + locations."/" = { + proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}"; + }; + }; + }; +} diff --git a/ops/modules/www/tazj.in.nix b/ops/modules/www/tazj.in.nix new file mode 100644 index 0000000000..3b80222e0d --- /dev/null +++ b/ops/modules/www/tazj.in.nix @@ -0,0 +1,49 @@ +# serve tazjin's website & blog +{ depot, config, lib, pkgs, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."tazj.in" = { + enableACME = true; + forceSSL = true; + root = depot.users.tazjin.homepage; + serverAliases = [ "www.tazj.in" ]; + + extraConfig = '' + location = /en/rss.xml { + return 301 https://tazj.in/feed.atom; + } + + ${depot.users.tazjin.blog.oldRedirects} + location /blog/ { + alias ${depot.users.tazjin.blog.rendered}/; + + if ($request_uri ~ ^/(.*)\.html$) { + return 302 /$1; + } + + try_files $uri $uri.html $uri/ =404; + } + + location = /predlozhnik { + return 302 https://predlozhnik.ru; + } + + # Temporary place for serving static files. + location /blobs/ { + alias /var/lib/tazjins-blobs/; + } + ''; + }; + + services.nginx.virtualHosts."git.tazj.in" = { + enableACME = true; + forceSSL = true; + extraConfig = "return 301 https://code.tvl.fyi$request_uri;"; + }; + }; +} diff --git a/ops/nixos/www/todo.tvl.fyi.nix b/ops/modules/www/todo.tvl.fyi.nix index 0820d136d2..b53f5437e7 100644 --- a/ops/nixos/www/todo.tvl.fyi.nix +++ b/ops/modules/www/todo.tvl.fyi.nix @@ -1,4 +1,4 @@ -{ config, ... }: +{ depot, ... }: { imports = [ @@ -8,7 +8,8 @@ config = { services.nginx.virtualHosts."todo.tvl.fyi" = { serverName = "todo.tvl.fyi"; - root = config.depot.web.todolist; + serverAliases = [ "todo.tvl.su" ]; + root = depot.web.todolist; enableACME = true; forceSSL = true; diff --git a/ops/modules/www/tvix.dev.nix b/ops/modules/www/tvix.dev.nix new file mode 100644 index 0000000000..f884bc30ed --- /dev/null +++ b/ops/modules/www/tvix.dev.nix @@ -0,0 +1,46 @@ +{ depot, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."tvix.dev" = { + serverName = "tvix.dev"; + enableACME = true; + forceSSL = true; + root = depot.tvix.website; + }; + + services.nginx.virtualHosts."bolt.tvix.dev" = { + root = depot.web.tvixbolt; + enableACME = true; + forceSSL = true; + }; + + # old domain, serve redirect + services.nginx.virtualHosts."tvixbolt.tvl.su" = { + enableACME = true; + forceSSL = true; + extraConfig = "return 301 https://bolt.tvix.dev$request_uri;"; + }; + + services.nginx.virtualHosts."docs.tvix.dev" = { + serverName = "docs.tvix.dev"; + enableACME = true; + forceSSL = true; + + extraConfig = '' + location = / { + # until we have a better default page here + return 301 https://docs.tvix.dev/rust/tvix_eval/index.html; + } + + location /rust/ { + alias ${depot.tvix.rust-docs}/; + } + ''; + }; + }; +} diff --git a/ops/nixos/www/tvl.fyi.nix b/ops/modules/www/tvl.fyi.nix index 9c2bf0274f..59ee1bc27f 100644 --- a/ops/nixos/www/tvl.fyi.nix +++ b/ops/modules/www/tvl.fyi.nix @@ -1,4 +1,4 @@ -{ config, ... }: +{ depot, ... }: { imports = [ @@ -8,7 +8,7 @@ config = { services.nginx.virtualHosts."tvl.fyi" = { serverName = "tvl.fyi"; - root = config.depot.web.tvl; + root = depot.web.tvl; enableACME = true; forceSSL = true; @@ -19,11 +19,28 @@ rewrite ^/monorepo-doc/?$ https://docs.google.com/document/d/1nnyByXcH0F6GOmEezNOUa2RFelpeRpDToBLYD_CtjWE/edit?usp=sharing last; - rewrite ^/irc/?$ ircs://chat.freenode.net:6697/##tvl last; + rewrite ^/irc/?$ ircs://irc.hackint.org:6697/#tvl last; + rewrite ^/webchat/?$ https://webirc.hackint.org/#ircs://irc.hackint.org/#tvl last; location ~* \.(webp|woff2)$ { add_header Cache-Control "public, max-age=31536000"; } + + location /blog { + if ($request_uri ~ ^/(.*)\.html$) { + return 302 /$1; + } + + try_files $uri $uri.html $uri/ =404; + } + + location = /blog { + return 302 /#blog; + } + + location = /blog/ { + return 302 /#blog; + } ''; }; }; diff --git a/ops/modules/www/tvl.su.nix b/ops/modules/www/tvl.su.nix new file mode 100644 index 0000000000..a7c4f6a217 --- /dev/null +++ b/ops/modules/www/tvl.su.nix @@ -0,0 +1,20 @@ +{ depot, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."tvl.su" = { + serverName = "tvl.su"; + root = depot.corp.website; + enableACME = true; + forceSSL = true; + + extraConfig = '' + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + ''; + }; + }; +} diff --git a/ops/modules/www/volgasprint.org.nix b/ops/modules/www/volgasprint.org.nix new file mode 100644 index 0000000000..7e5abe5561 --- /dev/null +++ b/ops/modules/www/volgasprint.org.nix @@ -0,0 +1,15 @@ +{ depot, ... }: + +{ + imports = [ + ./base.nix + ]; + + config = { + services.nginx.virtualHosts."volgasprint.org" = { + enableACME = true; + forceSSL = true; + root = "${depot.web.volgasprint}"; + }; + }; +} diff --git a/ops/nixos/www/wigglydonke.rs.nix b/ops/modules/www/wigglydonke.rs.nix index 0774eaea7c..6440164325 100644 --- a/ops/nixos/www/wigglydonke.rs.nix +++ b/ops/modules/www/wigglydonke.rs.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ depot, lib, pkgs, ... }: { imports = [ @@ -9,7 +9,7 @@ services.nginx.virtualHosts."wigglydonke.rs" = { enableACME = true; forceSSL = true; - root = "${config.depot.depotPath}/users/glittershark/wigglydonke.rs"; + root = "${depot.path + "/users/aspen/wigglydonke.rs"}"; }; }; } diff --git a/ops/modules/yandex-cloud.nix b/ops/modules/yandex-cloud.nix new file mode 100644 index 0000000000..cf6d1eb810 --- /dev/null +++ b/ops/modules/yandex-cloud.nix @@ -0,0 +1,78 @@ +# Profile for virtual machines on Yandex Cloud, intended for disk +# images. +# +# https://cloud.yandex.com/en/docs/compute/operations/image-create/custom-image +# +# TODO(tazjin): Upstream to nixpkgs once it works well. +{ config, lib, pkgs, modulesPath, ... }: + +let + cfg = config.virtualisation.yandexCloud; + + # Kernel modules required for interacting with the hypervisor. These + # must be available during stage 1 boot and during normal operation, + # as disks and network do not work without them. + modules = [ + "virtio-net" + "virtio-blk" + "virtio-pci" + "virtiofs" + ]; +in +{ + imports = [ + "${modulesPath}/profiles/headless.nix" + ]; + + options = { + virtualisation.yandexCloud.rootPartitionUuid = with lib; mkOption { + type = types.str; + default = "C55A5EE2-E5FA-485C-B3AE-CC928429AB6B"; + + description = '' + UUID to use for the root partition of the disk image. Yandex + Cloud requires that root partitions are mounted by UUID. + + Most users do not need to set this to a non-default value. + ''; + }; + }; + + config = { + fileSystems."/" = { + device = "/dev/disk/by-uuid/${lib.toLower cfg.rootPartitionUuid}"; + fsType = "ext4"; + autoResize = true; + }; + + boot = { + loader.grub.device = "/dev/vda"; + + initrd.kernelModules = modules; + kernelModules = modules; + kernelParams = [ + # Enable support for the serial console + "console=ttyS0" + ]; + + growPartition = true; + }; + + environment.etc.securetty = { + text = "ttyS0"; + mode = "0644"; + }; + + systemd.services."serial-getty@ttyS0".enable = true; + + services.openssh.enable = true; + + system.build.yandexCloudImage = import (pkgs.path + "/nixos/lib/make-disk-image.nix") { + inherit lib config pkgs; + additionalSpace = "128M"; + format = "qcow2"; + partitionTableType = "legacy+gpt"; + rootGPUID = cfg.rootPartitionUuid; + }; + }; +} diff --git a/ops/mq_cli/Cargo.lock b/ops/mq_cli/Cargo.lock index f418d77c34..18fed3621d 100644 --- a/ops/mq_cli/Cargo.lock +++ b/ops/mq_cli/Cargo.lock @@ -1,159 +1,168 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi", ] [[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.50" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.0" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "hermit-abi" -version = "0.1.6" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "libc" -version = "0.2.66" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] -name = "mq" -version = "1.0.0" +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", - "posix_mq 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", +] + +[[package]] +name = "mq_cli" +version = "3773.0.0" +dependencies = [ + "clap", + "libc", + "nix", + "posix_mq", ] [[package]] name = "nix" -version = "0.16.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", ] [[package]] name = "posix_mq" -version = "0.9.0" +version = "3771.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f462ad79a99ea13f3ef76d9c271956e924183f5aeb67a8649c8c2b6bdd079da8" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "nix", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "void" -version = "1.0.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" -"checksum posix_mq 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13ae339e13cc96902a4597a5aab6b76473093969c55d36ba33f6a7bf3268573f" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/ops/mq_cli/Cargo.toml b/ops/mq_cli/Cargo.toml index b412d88787..816a370759 100644 --- a/ops/mq_cli/Cargo.toml +++ b/ops/mq_cli/Cargo.toml @@ -1,10 +1,14 @@ [package] -name = "mq" -version = "1.0.0" -authors = ["Vincent Ambo <mail@tazj.in>"] +name = "mq_cli" +description = "CLI tool for accessing POSIX message queues (mq_overview(7))" +license = "MIT" +version = "3773.0.0" +authors = ["Vincent Ambo <tazjin@tvl.su>"] +homepage = "https://cs.tvl.fyi/depot/-/tree/ops/mq_cli" +repository = "https://code.tvl.fyi/depot.git:/ops/mq_cli.git" [dependencies] -clap = "2.33" +clap = "2.34" libc = "0.2" -nix = "0.16" -posix_mq = "0.9" +nix = "0.23" +posix_mq = "3771.0.0" diff --git a/ops/mq_cli/README.md b/ops/mq_cli/README.md index e612553e74..1045de896b 100644 --- a/ops/mq_cli/README.md +++ b/ops/mq_cli/README.md @@ -27,5 +27,16 @@ SUBCOMMANDS: send Send a message to a queue ``` +## Development + +Development happens in the [TVL +monorepo](https://cs.tvl.fyi/depot/-/tree/ops/mq_cli). + +Starting from version `3773.0.0`, the version numbers correspond to +_revisions_ of the TVL repository, available as git refs (e.g. +`refs/r/3773`). + +See the TVL documentation for more information about how to contribute +to the codebase. [POSIX message queues]: https://linux.die.net/man/7/mq_overview diff --git a/ops/mq_cli/src/main.rs b/ops/mq_cli/src/main.rs index 55ff006429..927993b486 100644 --- a/ops/mq_cli/src/main.rs +++ b/ops/mq_cli/src/main.rs @@ -1,36 +1,38 @@ extern crate clap; -extern crate posix_mq; extern crate libc; extern crate nix; +extern crate posix_mq; -use clap::{App, SubCommand, Arg, ArgMatches, AppSettings}; -use posix_mq::{Name, Queue, Message}; +use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use posix_mq::{Message, Name, Queue}; use std::fs::{read_dir, File}; use std::io::{self, Read, Write}; use std::process::exit; fn run_ls() { - let mqueues = read_dir("/dev/mqueue") - .expect("Could not read message queues"); + let mqueues = read_dir("/dev/mqueue").expect("Could not read message queues"); for queue in mqueues { let path = queue.unwrap().path(); let status = { - let mut file = File::open(&path) - .expect("Could not open queue file"); + let mut file = File::open(&path).expect("Could not open queue file"); let mut content = String::new(); - file.read_to_string(&mut content).expect("Could not read queue file"); + file.read_to_string(&mut content) + .expect("Could not read queue file"); content }; - let queue_name = path.components().last().unwrap() + let queue_name = path + .components() + .last() + .unwrap() .as_os_str() .to_string_lossy(); println!("/{}: {}", queue_name, status) - }; + } } fn run_inspect(queue_name: &str) { @@ -47,8 +49,7 @@ fn run_create(cmd: &ArgMatches) { set_rlimit(rlimit.parse().expect("Invalid rlimit value")); } - let name = Name::new(cmd.value_of("queue").unwrap()) - .expect("Invalid queue name"); + let name = Name::new(cmd.value_of("queue").unwrap()).expect("Invalid queue name"); let max_pending: i64 = cmd.value_of("max-pending").unwrap().parse().unwrap(); let max_size: i64 = cmd.value_of("max-size").unwrap().parse().unwrap(); @@ -56,11 +57,11 @@ fn run_create(cmd: &ArgMatches) { let queue = Queue::create(name, max_pending, max_size * 1024); match queue { - Ok(_) => println!("Queue created successfully"), + Ok(_) => println!("Queue created successfully"), Err(e) => { writeln!(io::stderr(), "Could not create queue: {}", e).ok(); exit(1); - }, + } }; } @@ -120,7 +121,12 @@ fn run_rlimit() { }; if errno != 0 { - writeln!(io::stderr(), "Could not get message queue rlimit: {}", errno).ok(); + writeln!( + io::stderr(), + "Could not get message queue rlimit: {}", + errno + ) + .ok(); } else { println!("Message queue rlimit:"); println!("Current limit: {}", rlimit.rlim_cur); @@ -170,16 +176,20 @@ fn main() { .about("Create a new queue") .arg(&queue_arg) .arg(&rlimit_arg) - .arg(Arg::with_name("max-size") - .help("maximum message size (in kB)") - .long("max-size") - .required(true) - .takes_value(true)) - .arg(Arg::with_name("max-pending") - .help("maximum # of pending messages") - .long("max-pending") - .required(true) - .takes_value(true)); + .arg( + Arg::with_name("max-size") + .help("maximum message size (in kB)") + .long("max-size") + .required(true) + .takes_value(true), + ) + .arg( + Arg::with_name("max-pending") + .help("maximum # of pending messages") + .long("max-pending") + .required(true) + .takes_value(true), + ); let receive = SubCommand::with_name("receive") .about("Receive a message from a queue") @@ -188,9 +198,11 @@ fn main() { let send = SubCommand::with_name("send") .about("Send a message to a queue") .arg(&queue_arg) - .arg(Arg::with_name("message") - .help("the message to send") - .required(true)); + .arg( + Arg::with_name("message") + .help("the message to send") + .required(true), + ); let rlimit = SubCommand::with_name("rlimit") .about("Get the message queue rlimit") @@ -211,13 +223,13 @@ fn main() { match matches.subcommand() { ("ls", _) => run_ls(), ("inspect", Some(cmd)) => run_inspect(cmd.value_of("queue").unwrap()), - ("create", Some(cmd)) => run_create(cmd), + ("create", Some(cmd)) => run_create(cmd), ("receive", Some(cmd)) => run_receive(cmd.value_of("queue").unwrap()), - ("send", Some(cmd)) => run_send( + ("send", Some(cmd)) => run_send( cmd.value_of("queue").unwrap(), - cmd.value_of("message").unwrap() + cmd.value_of("message").unwrap(), ), - ("rlimit", _) => run_rlimit(), + ("rlimit", _) => run_rlimit(), _ => unimplemented!(), } } diff --git a/ops/nixos.nix b/ops/nixos.nix new file mode 100644 index 0000000000..1442d89b30 --- /dev/null +++ b/ops/nixos.nix @@ -0,0 +1,67 @@ +# Helper functions for instantiating depot-compatible NixOS machines. +{ depot, lib, pkgs, ... }@args: + +let inherit (lib) findFirst isAttrs; +in rec { + # This provides our standard set of arguments to all NixOS modules. + baseModule = { ... }: { + # Ensure that pkgs == third_party.nix + nixpkgs.pkgs = depot.third_party.nixpkgs; + nix.nixPath = + let + # Due to nixpkgsBisectPath, pkgs.path is not always in the nix store + nixpkgsStorePath = + if lib.hasPrefix builtins.storeDir (toString pkgs.path) + then builtins.storePath pkgs.path # nixpkgs is already in the store + else pkgs.path; # we need to dump nixpkgs to the store either way + in + [ + ("nixos=" + nixpkgsStorePath) + ("nixpkgs=" + nixpkgsStorePath) + ]; + }; + + nixosFor = configuration: (depot.third_party.nixos { + configuration = { ... }: { + imports = [ + baseModule + configuration + ]; + }; + + specialArgs = { + inherit (args) depot; + }; + }); + + findSystem = hostname: + (findFirst + (system: system.config.networking.hostName == hostname) + (throw "${hostname} is not a known NixOS host") + (map nixosFor depot.ops.machines.all-systems)); + + rebuild-system = rebuildSystemWith ( + # HACK: use the string of the original source to avoid copying the whole + # depot into the store just for this + builtins.toString depot.path.origSrc); + + rebuildSystemWith = depotPath: pkgs.writeShellScriptBin "rebuild-system" '' + set -ue + if [[ $EUID -ne 0 ]]; then + echo "Oh no! Only root is allowed to rebuild the system!" >&2 + exit 1 + fi + + echo "Rebuilding NixOS for $HOSTNAME" + system=$(${pkgs.nix}/bin/nix-build -E "((import ${depotPath} {}).ops.nixos.findSystem \"$HOSTNAME\").system" --no-out-link --show-trace) + + ${pkgs.nix}/bin/nix-env -p /nix/var/nix/profiles/system --set $system + $system/bin/switch-to-configuration switch + ''; + + # Systems that should be built in CI + whitbySystem = (nixosFor depot.ops.machines.whitby).system; + sandunySystem = (nixosFor depot.ops.machines.sanduny).system; + nixeryDev01System = (nixosFor depot.ops.machines.nixery-01).system; + meta.ci.targets = [ "sandunySystem" "whitbySystem" "nixeryDev01System" ]; +} diff --git a/ops/nixos/.gitignore b/ops/nixos/.gitignore deleted file mode 100644 index 773fa16670..0000000000 --- a/ops/nixos/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -hardware-configuration.nix -local-configuration.nix -result diff --git a/ops/nixos/all-systems.nix b/ops/nixos/all-systems.nix deleted file mode 100644 index d1bf397462..0000000000 --- a/ops/nixos/all-systems.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ depot, ... }: - -(with depot.ops.nixos; [ - whitby -]) ++ - -(with depot.users.tazjin.nixos; [ - camden - frog -]) ++ - -(with depot.users.glittershark.system.system; [ - chupacabra - yeren -]) diff --git a/ops/nixos/default.nix b/ops/nixos/default.nix deleted file mode 100644 index 312d762f24..0000000000 --- a/ops/nixos/default.nix +++ /dev/null @@ -1,58 +0,0 @@ -# Most of the Nix expressions in this folder are NixOS modules, which -# are not readTree compatible. -# -# Some things (such as system configurations) are, and we import them -# here manually. -# -# TODO(tazjin): Find a more elegant solution for the whole module -# situation. -{ lib, pkgs, depot, ... }@args: - -let - inherit (lib) findFirst isAttrs; -in - -rec { - whitby = import ./whitby/default.nix args; - - # System installation - - allSystems = import ./all-systems.nix args; - - nixosFor = configuration: depot.third_party.nixos { - configuration = { - inherit depot; - imports = [ - configuration - "${depot.depotPath}/ops/nixos/depot.nix" - ]; - }; - }; - - findSystem = hostname: - (findFirst - (system: system.config.networking.hostName == hostname) - (throw "${hostname} is not a known NixOS host") - (map nixosFor allSystems)); - - rebuild-system = pkgs.writeShellScriptBin "rebuild-system" '' - set -ue - if [[ $EUID -ne 0 ]]; then - echo "Oh no! Only root is allowed to rebuild the system!" >&2 - exit 1 - fi - - echo "Rebuilding NixOS for $HOSTNAME" - system=$(nix-build -E "((import ${toString depot.depotPath} {}).ops.nixos.findSystem \"$HOSTNAME\").system" --no-out-link --show-trace) - - nix-env -p /nix/var/nix/profiles/system --set $system - $system/bin/switch-to-configuration switch - ''; - - # Systems that should be built in CI - # - # TODO(tazjin): Refactor the whole systems setup, it's a bit - # inconsistent at the moment. - whitbySystem = (nixosFor whitby).system; - meta.targets = [ "whitbySystem" ]; -} diff --git a/ops/nixos/depot.nix b/ops/nixos/depot.nix deleted file mode 100644 index 2c1b71a2da..0000000000 --- a/ops/nixos/depot.nix +++ /dev/null @@ -1,16 +0,0 @@ -# This module makes it possible to get at the depot from "proper" -# NixOS modules. -# -# It needs to be included and configured in each system like this: -# -# { -# imports = [ "${depot.depotPath}/ops/nixos/depot.nix" ]; -# inherit depot; -# } -{ lib, ... }: - -{ - options.depot = with lib; mkOption { - description = "tazjin's imported monorepo"; - }; -} diff --git a/ops/nixos/monorepo-gerrit.nix b/ops/nixos/monorepo-gerrit.nix deleted file mode 100644 index eda64766f4..0000000000 --- a/ops/nixos/monorepo-gerrit.nix +++ /dev/null @@ -1,123 +0,0 @@ -# Gerrit configuration for the TVL monorepo -{ pkgs, config, lib, ... }: - -let - cfg = config.services.gerrit; - gerritHooks = pkgs.runCommandNoCC "gerrit-hooks" {} '' - mkdir -p $out - ln -s ${config.depot.ops.besadii}/bin/besadii $out/ref-updated - ''; -in { - services.gerrit = { - enable = true; - listenAddress = "[::]:4778"; # 4778 - grrt - serverId = "4fdfa107-4df9-4596-8e0a-1d2bbdd96e36"; - builtinPlugins = [ - "download-commands" - "hooks" - ]; - - plugins = with config.depot.third_party.gerrit_plugins; [ - checks - owners - ]; - - package = config.depot.third_party.gerrit; - - jvmHeapLimit = "4g"; - - settings = { - core.packedGitLimit = "100m"; - log.jsonLogging = true; - log.textLogging = false; - sshd.advertisedAddress = "code.tvl.fyi:29418"; - hooks.path = "${gerritHooks}"; - cache.web_sessions.maxAge = "3 months"; - plugins.allowRemoteAdmin = false; - change.enableAttentionSet = true; - change.enableAssignee = false; - - # Configures gerrit for being reverse-proxied by nginx as per - # https://gerrit-review.googlesource.com/Documentation/config-reverseproxy.html - gerrit = { - canonicalWebUrl = "https://cl.tvl.fyi"; - docUrl = "/Documentation"; - }; - - httpd.listenUrl = "proxy-https://${cfg.listenAddress}"; - - download.command = [ - "checkout" - "cherry_pick" - "format_patch" - "pull" - ]; - - # Configure for Sourcegraph. - gitweb = { - type = "custom"; - url = "https://cs.tvl.fyi"; - linkname = "Sourcegraph"; - project = "/depot"; - revision = "/depot/-/commit/\${commit}"; - branch = "/depot@\${branch}"; - tag = "/depot@\${tag}"; - roottree = "/depot@\${commit}"; - file = "/depot@\${commit}/-/blob/\${file}"; - filehistory = "/depot@\${commit}/-/blob/\${file}#&tab=history"; - }; - - # Auto-link panettone bug links - commentlink.panettone = { - match = "b/(\\\\d+)"; - html = "<a href=\"https://b.tvl.fyi/issues/$1\">b/$1</a>"; - }; - - # Configures integration with the locally running OpenLDAP - auth.type = "LDAP"; - ldap = { - server = "ldap://localhost"; - accountBase = "ou=users,dc=tvl,dc=fyi"; - accountPattern = "(&(objectClass=organizationalPerson)(cn=\${username}))"; - accountFullName = "displayName"; - accountEmailAddress = "mail"; - accountSshUserName = "cn"; - groupBase = "ou=groups,dc=tvl,dc=fyi"; - - # TODO(tazjin): Assuming this is what we'll be doing ... - groupMemberPattern = "(&(objectClass=group)(member=\${dn}))"; - }; - - # Email sending (emails are relayed via the tazj.in domain's - # GSuite currently). - # - # Note that sendemail.smtpPass is stored in - # $site_path/etc/secure.config and is *not* controlled by Nix. - # - # Receiving email is not currently supported. - sendemail = { - enable = true; - html = false; - connectTimeout = "10sec"; - from = "TVL Code Review <tvlbot@tazj.in>"; - includeDiff = true; - smtpEncryption = "none"; - smtpServer = "localhost"; - smtpServerPort = 2525; - }; - }; - }; - - systemd.services.gerrit = { - serviceConfig = { - # There seems to be no easy way to get `DynamicUser` to play - # well with other services (e.g. by using SupplementaryGroups, - # which seem to have no effect) so we force the DynamicUser - # setting for the Gerrit service to be disabled and reuse the - # existing 'git' user. - DynamicUser = lib.mkForce false; - User = "git"; - Group = "git"; - }; - }; -} diff --git a/ops/nixos/tvl-slapd/default.nix b/ops/nixos/tvl-slapd/default.nix deleted file mode 100644 index b0234f30b2..0000000000 --- a/ops/nixos/tvl-slapd/default.nix +++ /dev/null @@ -1,217 +0,0 @@ -# Configures an OpenLDAP instance for TVL -# -# TODO(tazjin): Configure ldaps:// -{ config, lib, pkgs, ... }: - -with config.depot.nix.yants; - -let - user = struct { - username = string; - email = string; - password = string; - displayName = option string; - }; - - toLdif = defun [ user string ] (u: '' - dn: cn=${u.username},ou=users,dc=tvl,dc=fyi - objectClass: organizationalPerson - objectClass: inetOrgPerson - sn: ${u.username} - cn: ${u.username} - displayName: ${u.displayName or u.username} - mail: ${u.email} - userPassword: ${u.password} - ''); - - users = [ - { - username = "andi"; - email = "andi@notmuch.email"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$8lefg7+8UPAEh9Ott8zH0A$7YuLRraTC1IgxTNTxFJF03AWmqBS3GX2+vfD4XVTrb0"; - } - { - username = "artemist"; - email = "me@artem.ist"; - password = "{SSHA}N6Tl/txGQwlmVa7xVJCXpGcD1U4bJaI+"; - } - { - username = "camsbury"; - email = "camsbury7@gmail.com"; - password = "{SSHA}r6/I/zefrAb1jWTdhuqWik0CXT8E+/E5"; - } - { - username = "cynthia"; - email = "cynthia@tvl.fyi"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=4,p=1$TxjbMGenhEmkyYLrg5uGhbr60THB86YeRZg5bPdiTJo$k9gbRlAPjmxwdUwzbavvsAVkckgQZ0jS2oTtvZBPysk"; - } - { - username = "edef"; - email = "edef@edef.eu"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OORx4ERbkgvTmuYCJA8cIw$i5qaBzHkRVw7Tl+wZsTFTDqJwF0vuZqhW3VpknMYMc0"; - } - { - username = "ericvolp12"; - email = "ericvolp12@gmail.com"; - password = "{SSHA}pSepaQ+/5KBLfJtRR5rfxGU8goAsXgvk"; - } - { - username = "eta"; - email = "eta@theta.eu.org"; - password = "{SSHA}sOR5xzi7Lfv376XGQA8Hf6jyhTvo0XYc"; - } - { - username = "etu"; - email = "etu@failar.nu"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$RUrW8C9mWAkBSlkwSTH5dw$n3FXTeu41nDQfvJPI7TT3tcgwPmPJl8hPtaZ58qLq9A"; - } - { - username = "firefly"; - email = "firefly@firefly.nu"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$RYVVkFoi3A1yYkI8J2zUwg$GUERvgHvU8SGjQmilDJGZu50hYRAHw+ejtuL+Skygs8"; - } - { - username = "glittershark"; - email = "grfn@gws.fyi"; - password = "{SSHA}i7PSAsXwJT3jjmmvU77aar/tU/YPDCEO"; - } - { - username = "htbf"; - email = "h-tvl@htbf.dev"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$2iVXQQfd26icaIguHJg/CQ$hA9ziqn7kQ06AV6uQxJCGXoG8f+LWmH+nVlk00a1n/c"; - } - { - username = "isomer"; - email = "isomer@tvl.fyi"; - password = "{SSHA}OhWQkPJgH1rRJqYIaMUbbKC4iLEzvCev"; - } - { - username = "lukegb"; - email = "lukegb@tvl.fyi"; - password = "{SSHA}7a85VNhpFElFw+N5xcjgGmt4HnBsaGp4"; - } - { - username = "multi"; - email = "depot@in-addr.xyz"; - password = "{ARGON2}$argon2i$v=19$m=4096,t=3,p=1$qCfXhZUVft1YVPx7H4x7rw$dhtwtCrEMSpZfWQJbw2wpo5XHqiJqoZkiKeEbE6AdX0"; - } - { - username = "nyanotech"; - email = "nyanotechnology@gmail.com"; - password = "{SSHA}NIJ2RCRb1+Q4Bs63cyE91VZyiN47DG6y"; - } - { - username = "Profpatsch"; - email = "mail@profpatsch.de"; - password = "{SSHA}jcFXxRplMFxH4gpa0X5VdUzW64T95TwQ"; - } - { - username = "sterni"; - email = "sternenseemann@systemli.org"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$+NbF1izPMGqN5bASCBDV9g$aqBVplHwiyDpflZUmLtjkLWzKhxi7hwjm5fOwfbKohU"; - } - { - username = "q3k"; - email = "q3k@q3k.org"; - password = "{SSHA}BEccJdtnhVLDzOn+pxNfayNi3QFcEABE"; - } - { - username = "qyliss"; - displayName = "Alyssa Ross"; - email = "hi@alyssa.is"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$+uTpAKrN452D8wa7OFqPnw$GYi9/zns5iJCXDp1VuTPPsa35M5vkD6+rC8riT8cEHI"; - } - { - username = "riking"; - displayName = "kanepyork"; - email = "rikingcoding@gmail.com"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$o2OcfhfKOry+UrcmODyQCw$qloaQgoIRDESwaA3yqPxxy8sgLk3mrjYFBbF41elVrM"; - } - { - username = "tazjin"; - email = "mail@tazj.in"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wOPEl9D3kSke//oLtbvqrg$j0npwwXgaXQ/emefKUwL59tH8hdmtzbgH2rQzWSmE2Y"; - } - { - username = "implr"; - email = "implr@hackerspace.pl"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$SHRFps5sVgyUXYdmqGPw9g$tEx9DwKK1RjWlw52GLwOZ/iHep+QJboaZE83f1pXSwQ"; - } - { - username = "v"; - displayName = "V"; - email = "v@anomalous.eu"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$Wa11vk3gQKhJr1uzvtRTRQ$RHfvcC2j6rDUgWfezm05N03LeGIEezeKtmFmt+rfvM4"; - } - { - username = "ben"; - email = "tvl@benjojo.co.uk"; - password = "{SSHA}Zi48mSPsRMEPhff44w4RHi0SjjyhjWk1"; - } - { - username = "jamie"; - email = "jamie@kwiius.com"; - password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OkAMHVAfQ3nJhBffYJwk7Q$JV3DrF9eOU+4VL6I+nkaMUUOMqWuNzdp7N7U5Xwa3fg"; - } - ]; -in { - # Use our patched OpenLDAP derivation which enables stronger password hashing. - # - # Unfortunately the module for OpenLDAP has no package option, so we - # need to override it system-wide. Be aware that this triggers a - # *large* number of rebuilds of packages such as GPG and Python. - nixpkgs.overlays = [ - (_: _: { - inherit (config.depot.third_party) openldap; - }) - ]; - - services.openldap = { - enable = true; - dataDir = "/var/lib/openldap"; - database = "mdb"; - suffix = "dc=tvl,dc=fyi"; - rootdn = "cn=admin,dc=tvl,dc=fyi"; - rootpw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OfcgkOQ96VQ3aJj7NfA9vQ$oS6HQOkYl/bUYg4SejpltQYy7kvqx/RUxvoR4zo1vXU"; - - settings.children = { - "olcDatabase={1}mdb".attrs = { - objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; - olcDatabase = "{1}mdb"; - olcSuffix = "dc=tvl,dc=fyi"; - olcAccess = "to * by * read"; - }; - - "cn=module{0}".attrs = { - objectClass = "olcModuleList"; - olcModuleLoad = "pw-argon2"; - }; - }; - - # Contents are immutable at runtime, and adding user accounts etc. - # is done statically in the LDIF-formatted contents in this folder. - declarativeContents."dc=tvl,dc=fyi" = '' - dn: dc=tvl,dc=fyi - dc: tvl - o: TVL LDAP server - description: Root entry for tvl.fyi - objectClass: top - objectClass: dcObject - objectClass: organization - - dn: ou=users,dc=tvl,dc=fyi - ou: users - description: All users in TVL - objectClass: top - objectClass: organizationalUnit - - dn: ou=groups,dc=tvl,dc=fyi - ou: groups - description: All groups in TVL - objectClass: top - objectClass: organizationalUnit - - ${lib.concatStringsSep "\n" (map toLdif users)} - ''; - }; -} diff --git a/ops/nixos/tvl-sso/default.nix b/ops/nixos/tvl-sso/default.nix deleted file mode 100644 index 8590918e57..0000000000 --- a/ops/nixos/tvl-sso/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -# Configures an Apereo CAS instance for TVL SSO -{ config, ... }: - -let - inherit (config.depot.third_party) apereo-cas; -in { - config = { - environment.systemPackages = [ apereo-cas ]; - systemd.services.apereo-cas = { - description = "Apereo CAS Single Sign On server"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - serviceConfig = { - User = "apereo-cas"; - Group = "apereo-cas"; - ExecStart = "${apereo-cas}/bin/cas"; - EnvironmentFile = "/etc/cas/secrets"; - Restart = "always"; - }; - }; - users.users.apereo-cas = {}; - users.groups.apereo-cas = {}; - }; -} diff --git a/ops/nixos/v4l2loopback.nix b/ops/nixos/v4l2loopback.nix deleted file mode 100644 index 636b2ff6cf..0000000000 --- a/ops/nixos/v4l2loopback.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - boot = { - extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; - kernelModules = [ "v4l2loopback" ]; - extraModprobeConfig = '' - options v4l2loopback exclusive_caps=1 - ''; - }; -} - diff --git a/ops/nixos/whitby/OWNERS b/ops/nixos/whitby/OWNERS deleted file mode 100644 index b1b749e871..0000000000 --- a/ops/nixos/whitby/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -inherited: false - -# Want in on this list? Try paying! -owners: - - lukegb - - tazjin diff --git a/ops/nixos/whitby/default.nix b/ops/nixos/whitby/default.nix deleted file mode 100644 index 4210bcf57b..0000000000 --- a/ops/nixos/whitby/default.nix +++ /dev/null @@ -1,468 +0,0 @@ -{ depot, lib, ... }: - -let - inherit (builtins) listToAttrs; - inherit (lib) range; - - nixpkgs = import depot.third_party.nixpkgsSrc {}; - - # All Buildkite hooks are actually besadii, but it's being invoked - # with different names. - buildkiteHooks = depot.third_party.runCommandNoCC "buildkite-hooks" {} '' - mkdir -p $out/bin - ln -s ${depot.ops.besadii}/bin/besadii $out/bin/post-command - ''; -in lib.fix(self: { - inherit depot; - imports = [ - "${depot.depotPath}/ops/nixos/clbot.nix" - "${depot.depotPath}/ops/nixos/depot.nix" - "${depot.depotPath}/ops/nixos/irccat.nix" - "${depot.depotPath}/ops/nixos/monorepo-gerrit.nix" - "${depot.depotPath}/ops/nixos/panettone.nix" - "${depot.depotPath}/ops/nixos/paroxysm.nix" - "${depot.depotPath}/ops/nixos/smtprelay.nix" - "${depot.depotPath}/ops/nixos/sourcegraph.nix" - "${depot.depotPath}/ops/nixos/tvl-slapd/default.nix" - "${depot.depotPath}/ops/nixos/tvl-sso/default.nix" - "${depot.depotPath}/ops/nixos/www/cl.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/code.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/cs.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/login.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/todo.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/b.tvl.fyi.nix" - "${depot.depotPath}/ops/nixos/www/wigglydonke.rs.nix" - "${depot.third_party.nixpkgsSrc}/nixos/modules/services/web-apps/gerrit.nix" - ]; - - hardware = { - enableRedistributableFirmware = true; - cpu.amd.updateMicrocode = true; - }; - - boot = { - tmpOnTmpfs = true; - kernelModules = [ "kvm-amd" ]; - supportedFilesystems = [ "zfs" ]; - - initrd = { - availableKernelModules = [ - "igb" "xhci_pci" "nvme" "ahci" "usbhid" "usb_storage" "sr_mod" - ]; - - # Enable SSH in the initrd so that we can enter disk encryption - # passwords remotely. - network = { - enable = true; - ssh = { - enable = true; - port = 2222; - authorizedKeys = - depot.users.tazjin.keys.all - ++ depot.users.lukegb.keys.all - ++ [ depot.users.glittershark.keys.whitby ]; - - hostKeys = [ - /etc/secrets/initrd_host_ed25519_key - ]; - }; - - # this will launch the zfs password prompt on login and kill the - # other prompt - postCommands = '' - echo "zfs load-key -a && killall zfs" >> /root/.profile - ''; - }; - }; - - loader.grub = { - enable = true; - version = 2; - efiSupport = true; - efiInstallAsRemovable = true; - device = "/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_S439NA0N201620"; - }; - - zfs.requestEncryptionCredentials = true; - }; - - fileSystems = { - "/" = { - device = "zroot/root"; - fsType = "zfs"; - }; - - "/boot" = { - device = "/dev/disk/by-uuid/073E-7FBD"; - fsType = "vfat"; - }; - - "/nix" = { - device = "zroot/nix"; - fsType = "zfs"; - }; - - "/home" = { - device = "zroot/home"; - fsType = "zfs"; - }; - }; - - networking = { - # Glass is boring, but Luke doesn't like Wapping - the Prospect of - # Whitby, however, is quite a pleasant establishment. - hostName = "whitby"; - domain = "tvl.fyi"; - hostId = "b38ca543"; - useDHCP = false; - - # Don't use Hetzner's DNS servers. - nameservers = [ - "8.8.8.8" - "8.8.4.4" - ]; - - defaultGateway6 = { - address = "fe80::1"; - interface = "enp196s0"; - }; - - firewall.allowedTCPPorts = [ 22 80 443 4238 29418 ]; - - interfaces.enp196s0.useDHCP = true; - interfaces.enp196s0.ipv6.addresses = [ - { - address = "2a01:04f8:0242:5b21::feed:edef:beef"; - prefixLength = 64; - } - ]; - }; - - # Generate an immutable /etc/resolv.conf from the nameserver settings - # above (otherwise DHCP overwrites it): - environment.etc."resolv.conf" = with lib; { - source = depot.third_party.writeText "resolv.conf" '' - ${concatStringsSep "\n" (map (ns: "nameserver ${ns}") self.networking.nameservers)} - options edns0 - ''; - }; - - # Disable background git gc system-wide, as it has a tendency to break CI. - environment.etc."gitconfig".source = depot.third_party.writeText "gitconfig" '' - [gc] - autoDetach = false - ''; - - time.timeZone = "UTC"; - - nix = { - nrBuildUsers = 256; - maxJobs = lib.mkDefault 64; - extraOptions = '' - secret-key-files = /etc/secrets/nix-cache-privkey - ''; - - trustedUsers = [ - "grfn" - "lukegb" - "tazjin" - ]; - - sshServe = { - enable = true; - keys = with depot.users; - tazjin.keys.all - ++ lukegb.keys.all - ++ [ glittershark.keys.whitby ] - ++ multi.keys.whitbyNix; - }; - }; - - programs.mtr.enable = true; - programs.mosh.enable = true; - services.openssh = { - enable = true; - passwordAuthentication = false; - challengeResponseAuthentication = false; - }; - - # Run a handful of Buildkite agents to support parallel builds. - services.buildkite-agents = listToAttrs (map (n: rec { - name = "whitby-${toString n}"; - value = { - inherit name; - enable = true; - tokenPath = "/etc/secrets/buildkite-agent-token"; - hooks.post-command = "${buildkiteHooks}/bin/post-command"; - }; - }) (range 1 32)); - - # Start a local SMTP relay to Gmail (used by gerrit) - services.depot.smtprelay = { - enable = true; - args = { - listen = ":2525"; - remote_host = "smtp.gmail.com:587"; - remote_auth = "plain"; - remote_user = "tvlbot@tazj.in"; - }; - }; - - # Start the Gerrit->IRC bot - services.depot.clbot = { - enable = true; - - # Almost all configuration values are already correct (well, duh), - # see //fun/clbot for details. - flags = { - gerrit_host = "cl.tvl.fyi:29418"; - gerrit_ssh_auth_username = "clbot"; - gerrit_ssh_auth_key = "/etc/secrets/clbot-key"; - irc_server = "znc.lukegb.com:6697"; - - notify_branches = "canon,refs/meta/config"; - notify_repo = "depot"; - - # This secret is read from an environment variable, which is - # populated from /etc/secrets/clbot - irc_pass = "$CLBOT_PASS"; - }; - - channels = [ - "##tvl" - "##tvl-dev" - ]; - }; - - services.depot = { - # Run a SourceGraph code search instance - sourcegraph.enable = true; - - # Run the Panettone issue tracker - panettone = { - enable = true; - dbUser = "panettone"; - dbName = "panettone"; - secretsFile = "/etc/secrets/panettone"; - irccatChannel = "##tvl,##tvl-dev"; - }; - - # Run the first cursed bot (quote bot) - paroxysm.enable = true; - - # Run irccat to forward messages to IRC - irccat = { - enable = true; - config = { - tcp.listen = ":4722"; # "ircc" - irc = { - server = "chat.freenode.net:6697"; - tls = true; - nick = "tvlbot"; - realname = "TVL Bot"; - channels = [ - "##tvl" - "##tvl-dev" - ]; - }; - }; - }; - }; - - services.postgresql = { - enable = true; - enableTCPIP = true; - - authentication = lib.mkForce '' - local all all trust - host all all 127.0.0.1/32 password - host all all ::1/128 password - hostnossl all all 127.0.0.1/32 password - hostnossl all all ::1/128 password - ''; - - ensureDatabases = [ - "panettone" - ]; - - ensureUsers = [{ - name = "panettone"; - ensurePermissions = { - "DATABASE panettone" = "ALL PRIVILEGES"; - }; - }]; - }; - - services.postgresqlBackup = { - enable = true; - databases = [ - "tvldb" - "panettone" - ]; - }; - - environment.systemPackages = with nixpkgs; [ - bb - curl - emacs-nox - git - htop - nano - rxvt_unicode.terminfo - vim - zfs - zfstools - ]; - - # Run cgit for the depot. The onion here is nginx(thttpd(cgit)). - systemd.services.cgit = { - wantedBy = [ "multi-user.target" ]; - script = "${depot.web.cgit-taz}/bin/cgit-launch"; - - serviceConfig = { - Restart = "on-failure"; - User = "git"; - Group = "git"; - }; - }; - - # Regularly back up whitby to Google Cloud Storage. - systemd.services.restic = { - description = "Backups to Google Cloud Storage"; - script = "${nixpkgs.restic}/bin/restic backup /var/lib/gerrit /var/backup/postgresql"; - - environment = { - GOOGLE_PROJECT_ID = "tazjins-infrastructure"; - GOOGLE_APPLICATION_CREDENTIALS = "/var/backup/restic/gcp-key.json"; - RESTIC_REPOSITORY = "gs:tvl-fyi-backups:/whitby"; - RESTIC_PASSWORD_FILE = "/var/backup/restic/secret"; - RESTIC_CACHE_DIR = "/var/backup/restic/cache"; - RESTIC_EXCLUDE_FILE = builtins.toFile "exclude-files" '' - /var/lib/gerrit/tmp - ''; - }; - }; - - systemd.timers.restic = { - wantedBy = [ "multi-user.target" ]; - timerConfig.OnCalendar = "hourly"; - }; - - services.journaldriver = { - enable = true; - googleCloudProject = "tvl-fyi"; - logStream = "whitby"; - applicationCredentials = "/var/lib/journaldriver/key.json"; - }; - - security.sudo.extraRules = [ - { - groups = ["wheel"]; - commands = [{ command = "ALL"; options = ["NOPASSWD"]; }]; - } - ]; - - users = { - users.root.openssh.authorizedKeys.keys = [ - depot.users.tazjin.keys.frog - ]; - - users.tazjin = { - isNormalUser = true; - extraGroups = [ "git" "wheel" ]; - shell = nixpkgs.fish; - openssh.authorizedKeys.keys = depot.users.tazjin.keys.all; - }; - - users.lukegb = { - isNormalUser = true; - extraGroups = [ "git" "wheel" ]; - openssh.authorizedKeys.keys = depot.users.lukegb.keys.all; - }; - - users.grfn = { - isNormalUser = true; - extraGroups = [ "git" "wheel" ]; - openssh.authorizedKeys.keys = [ - depot.users.glittershark.keys.whitby - ]; - }; - - users.isomer = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.isomer.keys.all; - }; - - users.riking = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.riking.keys.u2f ++ depot.users.riking.keys.passworded; - }; - - users.edef = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.edef.keys.all; - }; - - users.qyliss = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.qyliss.keys.all; - }; - - users.multi = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.multi.keys.whitbyLogin; - }; - - users.eta = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.eta.keys.whitby; - }; - - users.v = { - isNormalUser = true; # Questionable... - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.v.keys.whitby; - }; - - users.cynthia = { - isNormalUser = true; # I'm normal OwO :3 - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.cynthia.keys.all; - }; - - users.firefly = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.firefly.keys.whitby; - }; - - users.sterni = { - isNormalUser = true; - extraGroups = [ "git" ]; - openssh.authorizedKeys.keys = depot.users.sterni.keys.all; - }; - - # Set up a user & group for git shenanigans - groups.git = {}; - users.git = { - group = "git"; - isNormalUser = false; - createHome = true; - home = "/var/lib/git"; - }; - }; - - security.acme = { - acceptTerms = true; - email = "mail@tazj.in"; - }; - - system.stateVersion = "20.03"; -}) diff --git a/ops/nixos/www/base.nix b/ops/nixos/www/base.nix deleted file mode 100644 index 4b956cd95e..0000000000 --- a/ops/nixos/www/base.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ config, pkgs, ... }: - -{ - config = { - services.nginx = { - enable = true; - enableReload = true; - - recommendedTlsSettings = true; - recommendedGzipSettings = true; - recommendedProxySettings = true; - }; - - # NixOS 20.03 broke nginx and I can't be bothered to debug it - # anymore, all solution attempts have failed, so here's a - # brute-force fix. - # - # TODO(tazjin): Find a link to the upstream issue and see if - # they've sorted it after ~20.09 - systemd.services.fix-nginx = { - script = "${pkgs.coreutils}/bin/chown -f -R nginx: /var/spool/nginx /var/cache/nginx"; - - serviceConfig = { - User = "root"; - Type = "oneshot"; - }; - }; - - systemd.timers.fix-nginx = { - wantedBy = [ "multi-user.target" ]; - timerConfig = { - OnCalendar = "minutely"; - }; - }; - }; -} diff --git a/ops/nixos/www/code.tvl.fyi.nix b/ops/nixos/www/code.tvl.fyi.nix deleted file mode 100644 index 5ee33f39ca..0000000000 --- a/ops/nixos/www/code.tvl.fyi.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ config, ... }: - -{ - imports = [ - ./base.nix - ]; - - config = { - services.nginx.virtualHosts.cgit = { - serverName = "code.tvl.fyi"; - enableACME = true; - forceSSL = true; - - extraConfig = '' - # Static assets must always hit the root. - location ~ ^/(favicon\.ico|cgit\.(css|png))$ { - proxy_pass http://localhost:2448; - } - - # Everything else hits the depot directly. - location / { - proxy_pass http://localhost:2448/cgit.cgi/depot/; - } - ''; - }; - }; -} diff --git a/ops/pipelines/depot.nix b/ops/pipelines/depot.nix index ec7fb81327..5eff622671 100644 --- a/ops/pipelines/depot.nix +++ b/ops/pipelines/depot.nix @@ -1,85 +1,40 @@ # This file configures the primary build pipeline used for the # top-level list of depot targets. -# -# It outputs a "YAML" (actually JSON) file which is evaluated and -# submitted to Buildkite at the start of each build. This means we can -# dynamically configure the pipeline execution here. -{ depot, lib, pkgs, ... }: +{ depot, pkgs, externalArgs, ... }: let - inherit (builtins) concatStringsSep foldl' map toJSON; - inherit (lib) singleton; - inherit (pkgs) writeText; - - # Create an expression that builds the target at the specified - # location. - mkBuildExpr = target: - let - descend = expr: attr: "builtins.getAttr \"${attr}\" (${expr})"; - targetExpr = foldl' descend "import ./. {}" target.__readTree; - subtargetExpr = descend targetExpr target.__subtarget; - in if target ? __subtarget then subtargetExpr else targetExpr; - - # Create a pipeline label from the targets tree location. - mkLabel = target: - let label = concatStringsSep "/" target.__readTree; - in if target ? __subtarget - then "${label}:${target.__subtarget}" - else label; - - # Create a pipeline step from a single target. - # - # If the build fails, Buildkite metadata is updated to mark the - # pipeline as failed. Buildkite has a concept of a failed pipeline - # regardless, but this data is not accessible. - mkStep = target: { - command = '' - nix-build -E '${mkBuildExpr target}' || (buildkite-agent meta-data set "failure" "1"; exit 1) - ''; - label = ":nix: ${mkLabel target}"; - }; - - # Protobuf check step which validates that changes to .proto files - # between revisions don't cause backwards-incompatible or otherwise - # flawed changes. - protoCheck = { - command = "${depot.nix.bufCheck}/bin/ci-buf-check"; - label = ":water_buffalo:"; - }; - - # This defines the build pipeline, using the pipeline format - # documented on https://buildkite.com/docs/pipelines/defining-steps - # - # Pipeline steps need to stay in order. - pipeline.steps = - # Zero the failure status - [ + pipeline = depot.nix.buildkite.mkPipeline { + headBranch = "refs/heads/canon"; + drvTargets = depot.ci.targets; + + parentTargetMap = + if (externalArgs ? parentTargetMap) + then builtins.fromJSON (builtins.readFile externalArgs.parentTargetMap) + else { }; + + postBuildSteps = [ + # After successful builds, create a gcroot for builds on canon. + # + # This anchors *most* of the depot, in practice it's unimportant + # if there is a build race and we get +-1 of the targets. + # + # Unfortunately this requires a third evaluation of the graph, but + # since it happens after :duck: it should not affect the timing of + # status reporting back to Gerrit. { - command = "buildkite-agent meta-data set 'failure' '0'"; - label = ":buildkite:"; + label = ":anchor:"; + branches = "refs/heads/canon"; + command = '' + nix-build -A ci.gcroot --out-link /nix/var/nix/gcroots/depot/canon + ''; } - { wait = null; } - ] - - # Create build steps for each CI target - ++ (map mkStep depot.ci.targets) - - ++ [ - # Simultaneously run protobuf checks - protoCheck - - # Wait for all previous checks to complete - ({ - wait = null; - continue_on_failure = true; - }) - - # Wait for all steps to complete, then exit with success or - # failure depending on whether any failure status was written. - # This step must be :duck:! (yes, really!) - ({ - command = "exit $(buildkite-agent meta-data get 'failure')"; - label = ":duck:"; - }) ]; -in (writeText "depot.yaml" (toJSON pipeline)) + }; + + drvmap = depot.nix.buildkite.mkDrvmap depot.ci.targets; +in +pkgs.runCommand "depot-pipeline" { } '' + mkdir $out + cp -r ${pipeline}/* $out + cp ${drvmap} $out/drvmap.json +'' diff --git a/ops/pipelines/fallback.yaml b/ops/pipelines/fallback.yaml deleted file mode 100644 index 73308d937b..0000000000 --- a/ops/pipelines/fallback.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# This build configuration provides a fallback which marks a build as -# failed. This is used if evaluating the build configuration fails, -# for example because of a syntax error in Nix code. ---- -steps: - - command: "echo 'Nix evaluation failed!' && exit 1" - # This step *must* be :duck: to trigger the correct hook. - label: ":duck:" diff --git a/ops/pipelines/static-pipeline.yaml b/ops/pipelines/static-pipeline.yaml index 515ab2cb64..af4f9d784e 100644 --- a/ops/pipelines/static-pipeline.yaml +++ b/ops/pipelines/static-pipeline.yaml @@ -1,15 +1,134 @@ -# This file defines the static pipeline which is uploaded in the -# Buildkite admin interface. These steps run at the beginning of each -# build and cause the dynamic pipeline generation to run. +# This file defines the static Buildkite pipeline which attempts to +# create the dynamic pipeline of all depot targets. +# +# If something fails during the creation of the pipeline, the fallback +# is executed instead which will simply report an error to Gerrit. --- +env: + BUILDKITE_TOKEN_PATH: /run/agenix/buildkite-graphql-token steps: + # Run pipeline for tvl-kit when new commits arrive on canon. Since + # it is not part of the depot build tree, this is a useful + # verification to ensure we don't break external things (too much). + - trigger: "tvl-kit" + async: true + label: ":fork:" + branches: "refs/heads/canon" + build: + message: "Verification triggered by ${BUILDKITE_COMMIT}" + + # Run pipeline for tvix when new commits arrive on canon. Since + # it is not part of the depot build tree, this is a useful + # verification to ensure we don't break external things (too much). + - trigger: "tvix" + async: true + label: ":fork:" + branches: "refs/heads/canon" + build: + message: "Verification triggered by ${BUILDKITE_COMMIT}" + + # Create a revision number for the current commit for builds on + # canon. + # + # This writes data back to Gerrit using the Buildkite agent + # credentials injected through a git credentials helper. + # + # Revision numbers are defined as the number of commits in the + # lineage of HEAD, following only the first parent of merges. + # + # Note that git does not fetch these refs by default, instead + # you'll have to modify your git config using + # `git config --add remote.origin.fetch '+refs/r/*:refs/r/*'`. + # The refs are available after the next `git fetch`. + - label: ":git:" + branches: "refs/heads/canon" + command: | + git -c 'credential.helper=gerrit-creds' \ + push origin "HEAD:refs/r/$(git rev-list --count --first-parent HEAD)" + + # Generate & upload dynamic build steps - label: ":llama:" + key: "pipeline-gen" + concurrency_group: 'depot-nix-eval' + concurrency: 5 # much more than this and whitby will OOM command: | - function fallback() { - echo 'Using fallback pipeline ...' - buildkite-agent pipeline upload ops/pipelines/fallback.yaml - exit - } + set -ue + + if test -n "$${GERRIT_CHANGE_URL-}"; then + echo "This is a build of [cl/$$GERRIT_CHANGE_ID]($$GERRIT_CHANGE_URL) (at patchset #$$GERRIT_PATCHSET)" | \ + buildkite-agent annotate --context cl-annotation + fi + + # Attempt to fetch a target map from a parent commit on canon, + # except on builds of canon itself. + [ "${BUILDKITE_BRANCH}" != "refs/heads/canon" ] && \ + nix/buildkite/fetch-parent-targets.sh + + PIPELINE_ARGS="" + if [[ -f tmp/parent-target-map.json ]]; then + PIPELINE_ARGS="--arg parentTargetMap tmp/parent-target-map.json" + fi + + nix-build --option restrict-eval true --include "depot=$${PWD}" \ + --include "store=/nix/store" \ + --allowed-uris 'https://' \ + -A ops.pipelines.depot \ + -o pipeline --show-trace $$PIPELINE_ARGS + + # Steps need to be uploaded in reverse order because pipeline + # upload prepends instead of appending. + ls pipeline/build-chunk-*.json | tac | while read chunk; do + buildkite-agent pipeline upload $$chunk + done + + buildkite-agent artifact upload "pipeline/*" + + # Wait for all previous steps to complete. + - wait: null + continue_on_failure: true + + # Exit with success or failure depending on whether any other steps + # failed. + # + # This information is checked by querying the Buildkite GraphQL API + # and fetching the count of failed steps. + # + # This step must be :duck: (yes, really!) because the post-command + # hook will inspect this name. + # + # Note that this step has requirements for the agent environment, which + # are enforced in our NixOS configuration: + # + # * curl and jq must be on the $PATH of build agents + # * besadii configuration must be readable to the build agents + - label: ":duck:" + key: ":duck:" + command: | + set -ueo pipefail + + readonly FAILED_JOBS=$(curl 'https://graphql.buildkite.com/v1' \ + --silent \ + -H "Authorization: Bearer $(cat ${BUILDKITE_TOKEN_PATH})" \ + -d "{\"query\": \"query BuildStatusQuery { build(uuid: \\\"$BUILDKITE_BUILD_ID\\\") { jobs(passed: false) { count } } }\"}" | \ + jq -r '.data.build.jobs.count') + + echo "$$FAILED_JOBS build jobs failed." + + if (( $$FAILED_JOBS > 0 )); then + exit 1 + fi + + # After duck, on success, upload and run any release steps that were + # output by the dynamic pipeline. + - label: ":arrow_heading_down:" + depends_on: + - step: ":duck:" + allow_failure: false + command: | + set -ueo pipefail + + buildkite-agent artifact download "pipeline/*" . - nix-build -A ops.pipelines.depot -o depot.yaml || fallback - buildkite-agent pipeline upload depot.yaml || fallback + find ./pipeline -name 'release-chunk-*.json' | tac | while read chunk; do + buildkite-agent pipeline upload $$chunk + done diff --git a/ops/posix_mq.rs/Cargo.lock b/ops/posix_mq.rs/Cargo.lock index fdd0086c4d..dc344613d0 100644 --- a/ops/posix_mq.rs/Cargo.lock +++ b/ops/posix_mq.rs/Cargo.lock @@ -1,54 +1,63 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "libc" -version = "0.2.66" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] -name = "nix" -version = "0.16.1" +name = "memoffset" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] -name = "posix_mq" -version = "0.9.0" +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", ] [[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +name = "posix_mq" +version = "3771.0.0" +dependencies = [ + "libc", + "nix", +] diff --git a/ops/posix_mq.rs/Cargo.toml b/ops/posix_mq.rs/Cargo.toml index d72e87a3dc..8390b80b86 100644 --- a/ops/posix_mq.rs/Cargo.toml +++ b/ops/posix_mq.rs/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "posix_mq" -version = "0.9.0" -authors = ["Vincent Ambo <mail@tazj.in>"] +version = "3771.0.0" +authors = ["Vincent Ambo <tazjin@tvl.su>"] description = "(Higher-level) Rust bindings to POSIX message queues" license = "MIT" -repository = "https://git.tazj.in/tree/ops/posix_mq.rs" +homepage = "https://cs.tvl.fyi/depot/-/tree/ops/posix_mq.rs" +repository = "https://code.tvl.fyi/depot.git:/ops/posix_mq.rs.git" [dependencies] -nix = "0.16" +nix = "0.23" libc = "0.2" diff --git a/ops/posix_mq.rs/README.md b/ops/posix_mq.rs/README.md index 9370c6c087..800d2221e4 100644 --- a/ops/posix_mq.rs/README.md +++ b/ops/posix_mq.rs/README.md @@ -1,7 +1,6 @@ posix_mq ======== -[![Build Status](https://travis-ci.org/aprilabank/posix_mq.rs.svg?branch=master)](https://travis-ci.org/aprilabank/posix_mq.rs) [![crates.io](https://img.shields.io/crates/v/posix_mq.svg)](https://crates.io/crates/posix_mq) This is a simple, relatively high-level library for the POSIX [message queue API][]. It wraps the lower-level API in a @@ -29,5 +28,17 @@ queue.send(&message).expect("message sending failed"); let result = queue.receive().expect("message receiving failed"); ``` +## Development + +Development happens in the [TVL +monorepo](https://cs.tvl.fyi/depot/-/tree/ops/posix_mq.rs). + +Starting from version `3771.0.0`, the version numbers correspond to +_revisions_ of the TVL repository, available as git refs (e.g. +`refs/r/3771`). + +See the TVL documentation for more information about how to contribute +to the codebase. + [message queue API]: https://linux.die.net/man/7/mq_overview [sister library]: https://github.com/aprilabank/posix_mq.kt diff --git a/ops/posix_mq.rs/src/error.rs b/ops/posix_mq.rs/src/error.rs index 1ef585c01e..bacd2aeb39 100644 --- a/ops/posix_mq.rs/src/error.rs +++ b/ops/posix_mq.rs/src/error.rs @@ -1,8 +1,5 @@ use nix; -use std::error; -use std::fmt; -use std::io; -use std::num; +use std::{error, fmt, io, num}; /// This module implements a simple error type to match the errors that can be thrown from the C /// functions as well as some extra errors resulting from internal validations. @@ -17,7 +14,7 @@ use std::num; /// * ENAMETOOLONG: This crate performs name validation /// /// If an unexpected error is encountered it will be wrapped appropriately and should be reported -/// as a bug on https://github.com/aprilabank/posix_mq.rs +/// as a bug on https://b.tvl.fyi #[derive(Debug)] pub enum Error { @@ -47,13 +44,13 @@ pub enum Error { // Some other unexpected / unknown error occured. This is probably an error from // the nix crate. Bug reports also welcome for this! - UnknownInternalError(Option<nix::Error>), + UnknownInternalError(), } -impl error::Error for Error { - fn description(&self) -> &str { +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; - match *self { + f.write_str(match *self { // This error contains more sensible description strings already InvalidQueueName(e) => e, ValueReadingError(_) => "error reading system configuration for message queues", @@ -67,31 +64,44 @@ impl error::Error for Error { QueueNotFound() => "the specified queue could not be found", InsufficientMemory() => "insufficient memory to call queue method", InsufficientSpace() => "insufficient space to call queue method", - ProcessFileDescriptorLimitReached() => - "maximum number of process file descriptors reached", - SystemFileDescriptorLimitReached() => - "maximum number of system file descriptors reached", + ProcessFileDescriptorLimitReached() => { + "maximum number of process file descriptors reached" + } + SystemFileDescriptorLimitReached() => { + "maximum number of system file descriptors reached" + } UnknownForeignError(_) => "unknown foreign error occured: please report a bug!", - UnknownInternalError(_) => "unknown internal error occured: please report a bug!", - } + UnknownInternalError() => "unknown internal error occured: please report a bug!", + }) } } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Explicitly import this to gain access to Error::description() - use std::error::Error; - f.write_str(self.description()) +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::ValueReadingError(e) => Some(e), + Error::UnknownForeignError(e) => Some(e), + _ => None, + } } } /// This from implementation is used to translate errors from the lower-level /// C-calls into sensible Rust errors. -impl From<nix::Error> for Error { - fn from(e: nix::Error) -> Self { - match e { - nix::Error::Sys(e) => match_errno(e), - _ => Error::UnknownInternalError(Some(e)), +impl From<nix::errno::Errno> for Error { + fn from(err: nix::Error) -> Self { + use nix::errno::Errno::*; + match err { + EACCES => Error::PermissionDenied(), + EBADF => Error::InvalidQueueDescriptor(), + EINTR => Error::QueueCallInterrupted(), + EEXIST => Error::QueueAlreadyExists(), + EMFILE => Error::ProcessFileDescriptorLimitReached(), + ENFILE => Error::SystemFileDescriptorLimitReached(), + ENOENT => Error::QueueNotFound(), + ENOMEM => Error::InsufficientMemory(), + ENOSPC => Error::InsufficientSpace(), + _ => Error::UnknownForeignError(err), } } } @@ -107,24 +117,6 @@ impl From<io::Error> for Error { // here because the system is probably seriously broken if those files don't contain numbers. impl From<num::ParseIntError> for Error { fn from(_: num::ParseIntError) -> Self { - Error::UnknownInternalError(None) - } -} - - -fn match_errno(err: nix::errno::Errno) -> Error { - use nix::errno::Errno::*; - - match err { - EACCES => Error::PermissionDenied(), - EBADF => Error::InvalidQueueDescriptor(), - EINTR => Error::QueueCallInterrupted(), - EEXIST => Error::QueueAlreadyExists(), - EMFILE => Error::ProcessFileDescriptorLimitReached(), - ENFILE => Error::SystemFileDescriptorLimitReached(), - ENOENT => Error::QueueNotFound(), - ENOMEM => Error::InsufficientMemory(), - ENOSPC => Error::InsufficientSpace(), - _ => Error::UnknownForeignError(err), + Error::UnknownInternalError() } } diff --git a/ops/posix_mq.rs/src/lib.rs b/ops/posix_mq.rs/src/lib.rs index 057601eccf..ed35fb03be 100644 --- a/ops/posix_mq.rs/src/lib.rs +++ b/ops/posix_mq.rs/src/lib.rs @@ -1,5 +1,5 @@ -extern crate nix; extern crate libc; +extern crate nix; use error::Error; use libc::mqd_t; @@ -8,8 +8,8 @@ use nix::sys::stat; use std::ffi::CString; use std::fs::File; use std::io::Read; -use std::string::ToString; use std::ops::Drop; +use std::string::ToString; pub mod error; @@ -33,16 +33,20 @@ impl Name { // have tried just using '/' as a queue name. if string.len() == 1 { return Err(Error::InvalidQueueName( - "Queue name must be a slash followed by one or more characters" + "Queue name must be a slash followed by one or more characters", )); } if string.len() > 255 { - return Err(Error::InvalidQueueName("Queue name must not exceed 255 characters")); + return Err(Error::InvalidQueueName( + "Queue name must not exceed 255 characters", + )); } if string.matches('/').count() > 1 { - return Err(Error::InvalidQueueName("Queue name can not contain more than one slash")); + return Err(Error::InvalidQueueName( + "Queue name can not contain more than one slash", + )); } // TODO: What error is being thrown away here? Is it possible? @@ -97,16 +101,9 @@ impl Queue { flags }; - let attr = mqueue::MqAttr::new( - 0, max_pending, max_size, 0 - ); + let attr = mqueue::MqAttr::new(0, max_pending, max_size, 0); - let queue_descriptor = mqueue::mq_open( - &name.0, - oflags, - default_mode(), - Some(&attr), - )?; + let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), Some(&attr))?; Ok(Queue { name, @@ -121,12 +118,7 @@ impl Queue { // No extra flags need to be constructed as the default is to open and fail if the // queue does not exist yet - which is what we want here. let oflags = mqueue::MQ_OFlag::O_RDWR; - let queue_descriptor = mqueue::mq_open( - &name.0, - oflags, - default_mode(), - None, - )?; + let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), None)?; let attr = mq_getattr(queue_descriptor)?; @@ -151,16 +143,9 @@ impl Queue { let default_pending = read_i64_from_file(MSG_DEFAULT)?; let default_size = read_i64_from_file(MSGSIZE_DEFAULT)?; - let attr = mqueue::MqAttr::new( - 0, default_pending, default_size, 0 - ); + let attr = mqueue::MqAttr::new(0, default_pending, default_size, 0); - let queue_descriptor = mqueue::mq_open( - &name.0, - oflags, - default_mode(), - Some(&attr), - )?; + let queue_descriptor = mqueue::mq_open(&name.0, oflags, default_mode(), Some(&attr))?; let actual_attr = mq_getattr(queue_descriptor)?; @@ -187,11 +172,8 @@ impl Queue { return Err(Error::MessageSizeExceeded()); } - mqueue::mq_send( - self.queue_descriptor, - msg.data.as_ref(), - msg.priority, - ).map_err(|e| e.into()) + mqueue::mq_send(self.queue_descriptor, msg.data.as_ref(), msg.priority) + .map_err(|e| e.into()) } /// Receive a message from the message queue. @@ -200,11 +182,7 @@ impl Queue { let mut data: Vec<u8> = vec![0; self.max_size as usize]; let mut priority: u32 = 0; - let msg_size = mqueue::mq_receive( - self.queue_descriptor, - data.as_mut(), - &mut priority, - )?; + let msg_size = mqueue::mq_receive(self.queue_descriptor, data.as_mut(), &mut priority)?; data.truncate(msg_size); Ok(Message { data, priority }) @@ -261,9 +239,9 @@ fn read_i64_from_file(name: &str) -> Result<i64, Error> { /// To work around it, this method calls the C-function directly. fn mq_getattr(mqd: mqd_t) -> Result<libc::mq_attr, Error> { use std::mem; - let mut attr = unsafe { mem::uninitialized::<libc::mq_attr>() }; - let res = unsafe { libc::mq_getattr(mqd, &mut attr) }; + let mut attr = mem::MaybeUninit::<libc::mq_attr>::uninit(); + let res = unsafe { libc::mq_getattr(mqd, attr.as_mut_ptr()) }; nix::errno::Errno::result(res) - .map(|_| attr) + .map(|_| unsafe { attr.assume_init() }) .map_err(|e| e.into()) } diff --git a/ops/posix_mq.rs/src/tests.rs b/ops/posix_mq.rs/src/tests.rs index 7a08876aea..1f4ea9a58d 100644 --- a/ops/posix_mq.rs/src/tests.rs +++ b/ops/posix_mq.rs/src/tests.rs @@ -4,8 +4,7 @@ use super::*; fn test_open_delete() { // Simple test with default queue settings let name = Name::new("/test-queue").unwrap(); - let queue = Queue::open_or_create(name) - .expect("Opening queue failed"); + let queue = Queue::open_or_create(name).expect("Opening queue failed"); let message = Message { data: "test-message".as_bytes().to_vec(), diff --git a/ops/secrets/.skip-subtree b/ops/secrets/.skip-subtree new file mode 100644 index 0000000000..80f63816f5 --- /dev/null +++ b/ops/secrets/.skip-subtree @@ -0,0 +1,2 @@ +The Nix configuration in here is read by agenix and not compatible +with readTree. diff --git a/ops/secrets/README.md b/ops/secrets/README.md new file mode 100644 index 0000000000..e59b865413 --- /dev/null +++ b/ops/secrets/README.md @@ -0,0 +1 @@ +TVL's deployment secrets, encrypted with [agenix](https://github.com/ryantm/agenix/commits/main) diff --git a/ops/secrets/besadii.age b/ops/secrets/besadii.age new file mode 100644 index 0000000000..50c2d1442d --- /dev/null +++ b/ops/secrets/besadii.age Binary files differdiff --git a/ops/secrets/buildkite-agent-token.age b/ops/secrets/buildkite-agent-token.age new file mode 100644 index 0000000000..66802310bb --- /dev/null +++ b/ops/secrets/buildkite-agent-token.age Binary files differdiff --git a/ops/secrets/buildkite-graphql-token.age b/ops/secrets/buildkite-graphql-token.age new file mode 100644 index 0000000000..6ebf3efca7 --- /dev/null +++ b/ops/secrets/buildkite-graphql-token.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw X7cI9stdU1F8M8Mhk/5a4UwU2Ze6rBXuwRDxUTKCTHw +CnksXNl+VEs2CYiucBeIgfpzpA05VshlECkbmTUZSpI +-> ssh-ed25519 zcCuhA 7KOsie4KRM0pPKZk8MeDISuX4tT9MAw/5mehSQcNOE8 +UfbpAlKJVhZOH5j4YIw5CVDen7UebTO/S55sLT9tVyc +-> ssh-ed25519 CpJBgQ EiDs9pCdSnPb4T4HvgF+gdyJ9f5orhtn1OVUp45e3jM +SlMWEzpi/mMlhfBPzVBn6jZknvjWCbRQMLoJEklJV2w +-> ssh-ed25519 aXKGcg kiuat73hEcxKvRZ9Gk115LjB3WVgd0h5KrjMOyTRLzw +CwEmQX6vmi6DnJp/TeYFOSdsfrprHylXAzhnAaQ3aKw +-> ssh-ed25519 OkGqLg R+moPPGckVPXrAnwQXFPqsizUwK+8UlL2VAA1965d1Y +J0sxPR2PDqK3k39dSLOzFQkUUZ5cfYqww6NHQ7E4ql4 +-> lb6ND/-grease !D$d P~ Tj. +HjRsXF0B07o957mq0zRgyHlckismT8UI8KcyFN55ff9FlWpci3+LEcPCb08wtraP +DSRvOi4 +--- AomJrDQJ4VQghgD6b7ItcPNyiu+cDmNQM31FOqYBbEk + 0:เนนXดฎ0bฅ^บ(ม๒:ฐำVฆr%GTฏh์ม>~ทถฟ บq๏กฺ*ผๅ ืชฝ;}$๘ \ No newline at end of file diff --git a/ops/secrets/buildkite-ssh-private-key.age b/ops/secrets/buildkite-ssh-private-key.age new file mode 100644 index 0000000000..c9aa988277 --- /dev/null +++ b/ops/secrets/buildkite-ssh-private-key.age Binary files differdiff --git a/ops/secrets/clbot-ssh.age b/ops/secrets/clbot-ssh.age new file mode 100644 index 0000000000..c24f8f45d3 --- /dev/null +++ b/ops/secrets/clbot-ssh.age Binary files differdiff --git a/ops/secrets/clbot.age b/ops/secrets/clbot.age new file mode 100644 index 0000000000..2cec1f7f36 --- /dev/null +++ b/ops/secrets/clbot.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw ZkAwxhi/ckHaVTnF7bmzOXhQG3HHqw1CpMe6nQL0rHc +9qnf0AY/inCEvk1VBd4RC3M0kATM/JuIyWxqisjersY +-> ssh-ed25519 zcCuhA o3PRUMcah5zjj39LtDWpgmBPFtHyx1N9WQz++lFrFEI +7K1kZHKfmlV5G/xVbgeOuLAO2iXKqcEyRYm+YfTvURs +-> ssh-ed25519 CpJBgQ pFnL2XmxzppshipadVltN/zSgiRiMh6emu6O8EZTpxI +K/RPjooKVSwqxc2aAUBtdTnkKoZvXDi+2NPB2NPXT9E +-> ssh-ed25519 aXKGcg sTN4w5iMnwxmp/E7OKu5I3pUc695OXBYmfOY8/hs1AM +DguaArDGVn7scD0NrDntgePjN1LFlfrPKfjEd1T9iOI +-> ssh-ed25519 OkGqLg xuRTDdql+UBNW2go+XxkC/FJZa+N/e6Kj/Fjm7MzG3E +KC39o7+WV+d/psN4mYSxeUSHsSCxPWTJgYjY1f1Dd3w +-> J:e-grease +CISPWfdtr4GKDU+lhCFk6B/EVyOmYwDxhChu +--- nwu3QYk6rfvIJWJrTB8RSBsWjS1uok8rSxc9FCzoA9k +WSMrฎ g#MSB๗}A"ึ๚๘จw}คูฏ๓วอ-่ลZแ1ศร๑ooGo8๗าจwรำ \ No newline at end of file diff --git a/ops/secrets/default.nix b/ops/secrets/default.nix new file mode 100644 index 0000000000..43f2a738bb --- /dev/null +++ b/ops/secrets/default.nix @@ -0,0 +1,3 @@ +args: +let mkSecrets = import ./mkSecrets.nix args; in +mkSecrets ./. (import ./secrets.nix) // { inherit mkSecrets; } diff --git a/ops/secrets/depot-inbox-imap.age b/ops/secrets/depot-inbox-imap.age new file mode 100644 index 0000000000..9bce1845cb --- /dev/null +++ b/ops/secrets/depot-inbox-imap.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw cpeIOVtFcfaHZpIAp495fkQLJoT++h1v6p0crBeuzFM ++zomKCg7UVNl/FlfcZflVPbo48C45uGoGoR1tbetEdk +-> ssh-ed25519 zcCuhA loSmQUCnO0EBaGg+wFYYkXOdLBQ6Z+pPl4Y3oGx6xzw ++RdXNYYtIDDXGr1Z0Mh28psvF9gzg12M3EJTUqmdFtU +-> ssh-ed25519 CpJBgQ 0W0LWu8WW6pQzUhK21CeNDUtW0srwR5gNCRjwTy94B4 +A02F+AyP+DajnVTJakx+0jynYRDix9I/9uZUDPjXpis +-> ssh-ed25519 aXKGcg SVBo2urAYGSYrlj3ieoi9nkrffcZ9ZroCn86pZkn4nI +xQRrLNeNcI9cpQY+X2xfLDoBqLNQixGjaYtMDWtHio4 +-> ssh-ed25519 BXptmQ UKNJPPjIiqPQndZ6/yASSg+5PQIn2N9nUy2hQMREq1Y +X9zM/ji9R3jLOEDGLpIVESjU13VU0e3cTAR1xEMhY5I +-> B-grease Y +vUOYknqY0okoUOKZD/8MpnpwkOU31sszuUZfeSVsuVyUMPEbFjWQT74 +--- ymKMaoUQXFPRc9U0ZvULBEC0Az0ew2oEyHwH/kR9ETI +Eu ซฏญxงแอำe_)zPบๅhำำส๙sฃGเ่ดสBLQ \ No newline at end of file diff --git a/ops/secrets/depot-replica-key.age b/ops/secrets/depot-replica-key.age new file mode 100644 index 0000000000..5e8ce94d5d --- /dev/null +++ b/ops/secrets/depot-replica-key.age Binary files differdiff --git a/ops/secrets/gerrit-autosubmit.age b/ops/secrets/gerrit-autosubmit.age new file mode 100644 index 0000000000..2e04be952d --- /dev/null +++ b/ops/secrets/gerrit-autosubmit.age Binary files differdiff --git a/ops/secrets/gerrit-secrets.age b/ops/secrets/gerrit-secrets.age new file mode 100644 index 0000000000..9ad123d578 --- /dev/null +++ b/ops/secrets/gerrit-secrets.age Binary files differdiff --git a/ops/secrets/grafana.age b/ops/secrets/grafana.age new file mode 100644 index 0000000000..eef349d64c --- /dev/null +++ b/ops/secrets/grafana.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw 0h55HIHm0kf6LqtI99LFUWBCoERBmpoF+anfnxjhDBU +0bHlgfRABn51BoMwAIjUlaVnCr3ZDXkQPmFOiIV3TvI +-> ssh-ed25519 zcCuhA 0vFMP1qFEiN4MUt+1qQCqtEovmO2d6QHj+KjHBrvqB4 +CUM2MDNPEKpksyCQmfDg/k/CKz7/ckgafw4aj0FLcmE +-> ssh-ed25519 CpJBgQ Y971kTqyElTHpOw4D7mUfkIQFWELOBeuGPUE6bqSrXQ +zt3ju2cqDfQJg9BsSsWcOGfPu5Q4XuIz0k2gasaRCPE +-> ssh-ed25519 aXKGcg eNxh3cCMbxG/u4luhlE2WQVzFMlZIcDKDx4dcpK43hY +HGJZYkWbYA0I7HtArCz9ErXwAAfOBHe20JH1J5Bx904 +-> ssh-ed25519 OkGqLg a1+l3dkThz8LLp7C1D9l7CzdB8Q4hxjNzaY7B6HMSnQ +du3nw0b61TGdF91Mq7C/PpjDlnIIph1dVEIivcDpM7M +-> \gwpw]-grease p#:x#sA ^S5*A/ ZpY +1rTU2Rc5MnpJj8zwOK4yR9HvDPOiKjCKHOURq6ak4SUmEgqqyqoujzRaL4I0cKf0 +zMFTkoKnLXjjLiHyvJWqCGwCRq9veUsTiJ6jqs+y6L+YaT71qDzDXi3YfX2p +--- hraNRaUxkHCnhk6AC/3jyxaAj1gyyIi0Q7cqoupcRrA +ก๛:ถ'!ซ37ซ s+0ป@มใืฏจฟd๊ ?๏!%๏lฌุดภอภ;ล๘๛ม2ขฟห๎กBพ!/gฝุใฑ/ฐ:wuีฏ๒ไ[ฉ~ฅณภั๗pฉFต \ No newline at end of file diff --git a/ops/secrets/irccat.age b/ops/secrets/irccat.age new file mode 100644 index 0000000000..2002b15c49 --- /dev/null +++ b/ops/secrets/irccat.age Binary files differdiff --git a/ops/secrets/journaldriver.age b/ops/secrets/journaldriver.age new file mode 100644 index 0000000000..c58773f36b --- /dev/null +++ b/ops/secrets/journaldriver.age Binary files differdiff --git a/ops/secrets/keycloak-db.age b/ops/secrets/keycloak-db.age new file mode 100644 index 0000000000..54194df183 --- /dev/null +++ b/ops/secrets/keycloak-db.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw tWBrwZf6FNYAHRjoVV9/X6gJCXPqxZSoA01dvIrIOzg +6W2A3smrrosM3sJgl5CT9vkCWqVKR3SaSxWS2nnwKJU +-> ssh-ed25519 zcCuhA IS0OcHfEfb01xe+FJUe1poruK+uuP0MaJpeoGYyVAFY +eEzcEYcW4KoKZZUEH/ha1nn9NudeK9HgPRgmrCWMjug +-> ssh-ed25519 CpJBgQ 4mjCHMHfnGu2bhANPBNmcrZQrKBcPgZU+ll8opmvGCk +0+Vd6pRPovUcKa9i37JVU/DUeYAmJ9D88MR4flA8gY8 +-> ssh-ed25519 aXKGcg WGCgCoViKLqndC35OTaExqZlPBDRwXRBJFuS7fw8n3Q +kUHunOUgIsxXmOzMCwUFF/0dYiae8YZGmgZaz8gXPJo +-> ssh-ed25519 OkGqLg LLIDJkImcqMjwRitnGevcav5YjDwYsQ//elx7fgbCQ4 +EnYTppSr/GKug9T+bFLGxrxUnNiXD5ODhB75OcH/h24 +-> j@-grease @:arA +8EFNz7i8N3gbZEMaQw +--- RkHJIg9pif/R47lgqrZD/XgkTETxXWkwW9QnFFsmfOA +ซoโ]ู~ฟ 6ห+j๘n]lี+๚ฺK=สฝ Zp9ข๓ฟยR์๐zVg u2ฬฬๆ_ \ No newline at end of file diff --git a/ops/secrets/mkSecrets.nix b/ops/secrets/mkSecrets.nix new file mode 100644 index 0000000000..c99130835f --- /dev/null +++ b/ops/secrets/mkSecrets.nix @@ -0,0 +1,27 @@ +# Expose secrets as part of the tree, making it possible to validate +# their paths at eval time. +# +# Note that encrypted secrets end up in the Nix store, but this is +# fine since they're publicly available anyways. +{ depot, lib, ... }: + +let + inherit (depot.nix.yants) + attrs + any + defun + list + path + restrict + string + struct + ; + ssh-pubkey = restrict "SSH pubkey" (lib.hasPrefix "ssh-") string; + agenixSecret = struct "agenixSecret" { publicKeys = list ssh-pubkey; }; +in + +defun [ path (attrs agenixSecret) (attrs any) ] + (path: secrets: + depot.nix.readTree.drvTargets + # Import each secret into the Nix store + (builtins.mapAttrs (name: _: "${path}/${name}") secrets)) diff --git a/ops/secrets/nix-cache-priv.age b/ops/secrets/nix-cache-priv.age new file mode 100644 index 0000000000..0381fb1290 --- /dev/null +++ b/ops/secrets/nix-cache-priv.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw 0Pp+oYDW8qhXoui/ewFhOTP10+JNOMS5qw66SuVHsXg +Usi+hC3pq8gzqp/taDJr2C+7fM1qxunhrngbyGrUMJ8 +-> ssh-ed25519 zcCuhA xO33hAmuSPrpYeZXX1saM6mPYL5M6biLtBrsxc73+is +S/pyKMUvn7zjjL3uIy3AJCkag4HpoOTh5SMYx/ZJ+rU +-> ssh-ed25519 CpJBgQ D1PyFsBzoKLMocbcQpy4PE7lFQGweoI7MJDuAzDRUhM +9+7ofW8vB3ZdS4A9nU0Rq+c4AJQPTZ0Bo/R3z1FY3io +-> ssh-ed25519 aXKGcg u7+l6RDdquEw0/e55x+Yx/W0+019qNsxJzR8DCkxwj0 +tseOgvoIQk5QG65IOqBg65n7ToFXTjHT+QhPT1/9PE0 +-> ssh-ed25519 OkGqLg Hsk569u9xxHWQZKNqqxpQbFaX4KDjS9VRqE808vh/kA +kiaoD3XCcrqfYEbneU+L7b2yPHo6ioUhtpxI9uEVnJw +-> a{7<M_-grease k~MV B{E[ +sc3e +--- dvGSRVY+ZDyS4cLqY8yguVZraB/IZSPaexlGMKLvnlQ +ฆ(wฺำ๛ฮ/ำ๗W$|Qฆjไ์ฐฝz^ ]ฝฃm$ฆช%ฅ.xKฏKกแจl[nฐO75ชwiผ>แม๕g#q4^k;\ehX"wฌ๊ภฦไวภ` 9ฐำ_ว๐Cศo๒qชจฐท9mtๅภK5ข \ No newline at end of file diff --git a/ops/secrets/nix-cache-pub.age b/ops/secrets/nix-cache-pub.age new file mode 100644 index 0000000000..ae06f49d69 --- /dev/null +++ b/ops/secrets/nix-cache-pub.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw +jfxfM1YDu5CoYtFeRWtpkUQhmFWn/kNBYsBnie7BVg +XxL9l87hXD0zCUEwbSR9OHSYgpOw89Km5iyxPPnVDGQ +-> ssh-ed25519 zcCuhA VAoDkN2gwErUFE/59V4IF9PbSBSleOjt2gosvYnHxWg +Pf6eh8EfAdATjZIkQfhhqOXuJXIdwIpybITcn+rcutI +-> ssh-ed25519 CpJBgQ C6zIv78gu+wBeAjhmXANegSNqGHnugemXBPQcTimgxg +80109g83Hk+smWuZkTIZJ6VFQqJ+LU1boWKQIH1AHjc +-> ssh-ed25519 aXKGcg lPb+kGr0vuJkQO6VutAm4Yh1CVi/XfqNdGbAh/B7ZRk +h4xb++7I9iv8208oqY0xLruA1r62mepISFcusczdbgs +-> ssh-ed25519 OkGqLg aOHt9OR8JChtYpclkgn9wCFnlayFje7WsMGQb8AqChU +3VRTDMUwFtDcoxGU/wiBzTvS0SB/xOpBG6s+ENvAXVE +-> Kow$7|\-grease +8OGnQnY7gm4vMJRXjnBogA0HRU7hqIxs2sErFc7sV1CUNkZlFjdK8tZomlNwshjc +p18HgtjJnaGhSqg1LyP7cJAo/XnSwDYCeNna/6vdlKBR3JeuOGTmx1NIG/cGSg +--- w+jJplb/J3av+UcltcFf4qSqHoQ8Ol8lH/fFB3051Gw +qIํe:1*`j8๕ฑsบnHcyฮเ7ฃฒศรตๅ(ชใพ.xDธ_}%๓)P,Dๆำ6ซSอ้Hล๊รU9ฐ๋ฌิ0ํ8อิํ\ณ๖' \ No newline at end of file diff --git a/ops/secrets/owothia.age b/ops/secrets/owothia.age new file mode 100644 index 0000000000..177ee61383 --- /dev/null +++ b/ops/secrets/owothia.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw 8XtdgZ++/ZqmK4j8CO8oiuskTxjvKhWDK7fet5hbqiM +Fs4O1vFtQL1JamnuCMPLzfzRPb90nxfXB6OXkyCMoHo +-> ssh-ed25519 zcCuhA 6PNsPMdRXM77ci+mBQNRxr1oMGDNdlQilpUB0Q5es28 +APw2L/0htM9U0fJ1IUthdkoem/UTM/6NNQrgn4Vmpcs +-> ssh-ed25519 CpJBgQ ed00il0q23M+3KH6hf5fFPaXGUKcz03Bn01jSoKiB1U +jEN0Dk2edJBQreAlNE11sx0cI5u1mfFDT11Ev0KJ+gs +-> ssh-ed25519 aXKGcg NocBhG6QGlWDZhjsA6Sxvjv9Gs+3Pq5gcOqnVdiefBg +HYnqBv0pdPz8bqgZ98VDfYFeKcFNeuJrlOsyWt551Sg +-> ssh-ed25519 OkGqLg e0081m/IkQafXh1gAWUZ2glYG7bklCG/LaUy63rK6gc +G2RNMxCxRnqocYhiq142T8EPZQD8cRHHs7AHKFrMLaU +-> +J}@hPk-grease +406BMfqUt/KjayTopj4dNa4owPZphR6AsBXPurJwU/zV9ipirfW3oEeaprdh4uLg +RHO0bSZQV1uu1YmbXkuwMaVj1cVn2vsDPEv3xG2SRzMoEpAAKaFCBba8 +--- 5ncLI9pS25vz5CebIZjPPDQ5cHISlyRFF55rGgFQnnM +แ$"PKช&Cท\ๅ(ีGภ[ฯ( ฌทD๐ึlวบน8ก4็๎ฎPฦ)ฉตฌนบBขฎิผฃฎ๑c\ฅU:๘สฌ( \ No newline at end of file diff --git a/ops/secrets/panettone.age b/ops/secrets/panettone.age new file mode 100644 index 0000000000..0be42dc0a7 --- /dev/null +++ b/ops/secrets/panettone.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw zzUe0JqhICtd/kgZnXFpwaQ1Ma6nqy/hMWaOJpRHmDs +4cR+OnWShG6MpB/u0yfsSxplEch7x7DbygfBiJGxOOs +-> ssh-ed25519 zcCuhA 0RZEYC9IuazO9fROalwoOCIgc0j+rNBP3gw7SKG0yEw +mPRhN0hvccEr1A9ihWAFMH4/24vpBKpxBVq4BKBMmYM +-> ssh-ed25519 CpJBgQ VrmfTtTVxuQmpUxMxtXtCnr8pFyqwtdyLHdbzYrlKlM +kHgEdPmoIOLnGuMF5F5Ol1yZWcactSE4OZI0BSmDN+g +-> ssh-ed25519 aXKGcg On4jwgsH504ZjYRwfw5oAfIDk3wU0+xgd43ryAn9H0I +fayzht1ZPPiFCjuYTdwVtJu2nOUg4wtp5IipOR4oJm8 +-> ssh-ed25519 OkGqLg mubp0xI0fvsKOAUaNaftFkHJ+bxgFHbgjn+A7sR8XVs +X68Zr8HvC4/XPC0AFIA5f1SKu7NSR/23oeX8cW1qfis +-> ?`-grease +hOy2Rwvk6+vXpHWWA49Wp10wKbw9TfsLXw +--- 9MLGx6BVm40C0CSV3bq6dnXrpy3QunBlh2/uO5OisUU +วณG<ีๅมะYืA๗Vsณ๐/-%gช๚.e@,Z๑ๆFWๆถ&ๆ๎ง๓<O๖q@พ>wๅฬQ>-gว'ฉฬ`กถจX๖าฯP8ณx<RNvท9ื#'/)ภฆgฆ๚่m2๕ฉิv๐<,฿7 ๗ใษ้ขะวqฏฆชv็QปทAOฮ-๓ฺ+gๅcส#ตๅฝ๎ข*ขฐeํ -งท)า ๙; \ No newline at end of file diff --git a/ops/secrets/secrets.nix b/ops/secrets/secrets.nix new file mode 100644 index 0000000000..5cbf2bf612 --- /dev/null +++ b/ops/secrets/secrets.nix @@ -0,0 +1,54 @@ +let + flokli = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli" + ]; + + tazjin = [ + # tverskoy + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM1fGWz/gsq+ZeZXjvUrV+pBlanw1c3zJ9kLTax9FWQy" + + # zamalek + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDBRXeb8EuecLHP0bW4zuebXp4KRnXgJTZfeVWXQ1n1R" + ]; + + aspen = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMcBGBoWd5pPIIQQP52rcFOQN3wAY0J/+K2fuU6SffjA " + ]; + + sterni = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk+KvgvI2oJTppMASNUfMcMkA2G5ZNt+HnWDzaXKLlo" + ]; + + sanduny = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOag0XhylaTVhmT6HB8EN2Fv5Ymrc4ZfypOXONUkykTX"; + whitby = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNh/w4BSKov0jdz3gKBc98tpoLta5bb87fQXWBhAl2I"; + + terraform.publicKeys = tazjin ++ aspen ++ sterni ++ flokli; + whitbyDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ whitby ]; + allDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ sanduny whitby ]; + sandunyDefault.publicKeys = tazjin ++ aspen ++ sterni ++ [ sanduny ]; +in +{ + "besadii.age" = whitbyDefault; + "buildkite-agent-token.age" = whitbyDefault; + "buildkite-graphql-token.age" = whitbyDefault; + "buildkite-ssh-private-key.age" = whitbyDefault; + "clbot-ssh.age" = whitbyDefault; + "clbot.age" = whitbyDefault; + "depot-inbox-imap.age" = sandunyDefault; + "depot-replica-key.age" = whitbyDefault; + "gerrit-autosubmit.age" = whitbyDefault; + "gerrit-secrets.age" = whitbyDefault; + "grafana.age" = whitbyDefault; + "irccat.age" = whitbyDefault; + "journaldriver.age" = allDefault; + "keycloak-db.age" = whitbyDefault; + "nix-cache-priv.age" = whitbyDefault; + "nix-cache-pub.age" = whitbyDefault; + "owothia.age" = whitbyDefault; + "panettone.age" = whitbyDefault; + "smtprelay.age" = whitbyDefault; + "tf-buildkite.age" = terraform; + "tf-glesys.age" = terraform; + "tf-keycloak.age" = terraform; + "tvl-alerts-bot-telegram-token.age" = whitbyDefault; +} diff --git a/ops/secrets/smtprelay.age b/ops/secrets/smtprelay.age new file mode 100644 index 0000000000..62fbaffadf --- /dev/null +++ b/ops/secrets/smtprelay.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw CW2Lgm0tSWUDwKSNSX/aLkVzQ/QeEeQgU3NITpz2D0M +F7dA+zWdCz21s443bj9zCz6lBsRlFIxiG+l8CdbuPFk +-> ssh-ed25519 zcCuhA l8rsBoYDwhUB5stbeGXYTQ4Fz745ywXFCOQZn2cMBW0 +TycVcUZjR2TDv5DPC54+RwoU6Fj4QpRUJj1j0HM/JCE +-> ssh-ed25519 CpJBgQ CbwZO5LmSxd0HRYkf+lV+ymFcXSn/49GAPHG4l1I7gw +xSmab5+BnAZF/B0n32xX1qZPdHgfoEMGIuZqlpnISjc +-> ssh-ed25519 aXKGcg Tr+odf9p1RBrQK1guR6ToeN4wG1KLA3jwiPIkgyEjws +TaeCnjiRp8VZoMS5qs+OfVbBc6zudayD693h/eGvVOo +-> ssh-ed25519 OkGqLg Dmnsqz6PKzMd6w4t+l6+EWuia+stPwSEtu00KVuAojo +rZ/i1WJhrCM/ZQTAroRRSjzUVJw2UJlPUe1uHYqSscw +-> w!^Z-grease i86O2 i0.Rch +/zsRadAGYzAY6F/J5m6lMjmojkN7NbY3TbfQbA +--- /rQgwuY9SVGLKeUzY5P6c+sGQ1I1aw5cQxmO46QKDSQ + ้(`ฏฏคU ฌ๙,ใรcผ้|าPๆ็ ฟ9แ@& ซวgM฿ +CHโ3ik๗มฤ3#|ๅึgธMาึณAดgขAึ๚nZ๓วYโtจุ๛ฏฬ2นฑK2 Yฺ \ No newline at end of file diff --git a/ops/secrets/tf-buildkite.age b/ops/secrets/tf-buildkite.age new file mode 100644 index 0000000000..0cf6066fa6 --- /dev/null +++ b/ops/secrets/tf-buildkite.age Binary files differdiff --git a/ops/secrets/tf-glesys.age b/ops/secrets/tf-glesys.age new file mode 100644 index 0000000000..4e50454b62 --- /dev/null +++ b/ops/secrets/tf-glesys.age Binary files differdiff --git a/ops/secrets/tf-keycloak.age b/ops/secrets/tf-keycloak.age new file mode 100644 index 0000000000..237b9377bd --- /dev/null +++ b/ops/secrets/tf-keycloak.age Binary files differdiff --git a/ops/secrets/tvl-alerts-bot-telegram-token.age b/ops/secrets/tvl-alerts-bot-telegram-token.age new file mode 100644 index 0000000000..e897fedc03 --- /dev/null +++ b/ops/secrets/tvl-alerts-bot-telegram-token.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> ssh-ed25519 dcsaLw JGXCnhez0LnlUV8eOitxizmxw/gV+1taBRhNvwvVcms +qsRTOpifnoc0eorFjd4UlP7O3hkRR3KjDUcImASK0jY +-> ssh-ed25519 zcCuhA KUcyaHcmuqCGtJBzvc2UK17gRrjzuzIxll+TS9Q4nWs +CAJ19ClA9Tqj1fcYySq+K9gdZe6Uv0toZLnhlovr3tM +-> ssh-ed25519 CpJBgQ OAE+u9JuC6KoefjCOTj4NkQElZRe6/EEIAGBN/XelnU +M9MHlKxbEBJ+gACo2FiYqmm1cAoYW31+nP16qnVZ7Zw +-> ssh-ed25519 aXKGcg Ll6v6v5HpUIEuOzjpVsPMmPQMnNkmyB4fz/YwNXfCHU +MmFQy2WkKn5SM0bhe4NNe/lMnneKoOF+Ufq0t0QjNbw +-> ssh-ed25519 OkGqLg PS6KLwat1z2BSQ9sIKDaryVU39EJR+iiAaKSP/KSPk0 +qUQP2f4MFk83zQ9edlSNC8jwpJvmp2xhOysd8rnYzW4 +-> >NI-grease @mOcHT z|%,s- mw^c * +zu0M2pS6v3zehnLg +--- jltBYy9brAtpkEIqPoGmIVe3s5XnWtpa9EmuXlAf91c +tdX2-น"ฤำ#ฦ1ํn'\๘'{Dlw;Pึดะ@ฺฬ{๙฿B !yฃ+x๕หะํWตถฤB:wtูqph \ No newline at end of file diff --git a/ops/terraform/README.md b/ops/terraform/README.md new file mode 100644 index 0000000000..9ff6c23d47 --- /dev/null +++ b/ops/terraform/README.md @@ -0,0 +1,5 @@ +//ops/terraform +=============== + +This folder contains Terraform modules and other related +Terraform-tooling by TVL. diff --git a/ops/terraform/deploy-nixos/README.md b/ops/terraform/deploy-nixos/README.md new file mode 100644 index 0000000000..fd0bd1b442 --- /dev/null +++ b/ops/terraform/deploy-nixos/README.md @@ -0,0 +1,50 @@ +<!-- +SPDX-FileCopyrightText: 2023 The TVL Authors + +SPDX-License-Identifier: MIT +--> + +deploy-nixos +============ + +This is a Terraform module to deploy a NixOS system closure to a +remote machine. + +The system closure must be accessible by Nix-importing the repository +root and building a specific attribute +(e.g. `nix-build -A ops.machines.machine-name`). + +The target machine must be accessible normally over SSH, and an SSH +key must be used for access. + +Notably this module separates the evaluation of the system closure from building +and deploying it, and uses the closure's derivation hash to determine whether a +deploy is necessary. + +## Usage example: + +```terraform +module "deploy_somehost" { + source = "git::https://code.tvl.fyi/depot.git:/ops/terraform/deploy-nixos.git" + attrpath = "ops.nixos.somehost" + target_host = "somehost.tvl.su" + target_user = "someone" + target_user_ssh_key = tls_private_key.somehost.private_key_pem +} +``` + +## Future work + +Several things can be improved about this module, for example: + +* The repository root (relative to which the attribute path is evaluated) could + be made configurable. + +* The remote system closure could be discovered to restore remote system state + after manual deploys on the target (i.e. "stomping" of changes). + +More ideas and contributions are, of course, welcome. + +## Acknowledgements + +Development of this module was sponsored by [Resoptima](https://resoptima.com/). diff --git a/ops/terraform/deploy-nixos/main.tf b/ops/terraform/deploy-nixos/main.tf new file mode 100644 index 0000000000..50278b248e --- /dev/null +++ b/ops/terraform/deploy-nixos/main.tf @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2023 The TVL Authors +# +# SPDX-License-Identifier: MIT + +# This module deploys a NixOS host by building a system closure +# located at the specified attribute in the current repository. +# +# The closure's derivation path is persisted in the Terraform state to +# determine after Nix evaluation whether the system closure has +# changed and needs to be built/deployed. +# +# The system configuration is then built (or substituted) on the +# machine that runs `terraform apply`, then copied and activated on +# the target machine using `nix-copy-closure`. + +variable "attrpath" { + description = "attribute set path pointing to the NixOS system closure" + type = string +} + +variable "target_host" { + description = "address (IP or hostname) at which the target is reachable" + type = string +} + +variable "entrypoint" { + description = <<EOT + Path to a .nix file (or directory containing `default.nix` file) + that provides the attrset specified in `closure`. + If unset, asks git for the root of the repository. + EOT + type = string + default = "" +} + +variable "target_user" { + description = "username on the target machine" + type = string +} + +variable "target_user_ssh_key" { + description = "SSH key to use for connecting to the target" + type = string + default = "" + sensitive = true +} + +variable "triggers" { + type = map(string) + description = "Triggers for deploy" + default = {} +} + +# Fetch the derivation hash for the NixOS system. +data "external" "nixos_system" { + program = ["${path.module}/nix-eval.sh"] + + query = { + attrpath = var.attrpath + entrypoint = var.entrypoint + } +} + +# Deploy the NixOS configuration if anything changed. +resource "null_resource" "nixos_deploy" { + connection { + type = "ssh" + host = var.target_host + user = var.target_user + private_key = var.target_user_ssh_key + } + + # 1. Wait for SSH to become available. + provisioner "remote-exec" { + inline = ["true"] + } + + # 2. Build NixOS system. + provisioner "local-exec" { + command = "nix-build ${data.external.nixos_system.result.drv} --no-out-link" + } + + # 3. Copy closure to the target. + provisioner "local-exec" { + command = "${path.module}/nixos-copy.sh" + + environment = { + SYSTEM_DRV = data.external.nixos_system.result.drv + TARGET_HOST = var.target_host + DEPLOY_KEY = var.target_user_ssh_key + TARGET_USER = var.target_user + } + } + + # 4. Activate closure on the target. + provisioner "remote-exec" { + inline = [ + "set -eu", + "SYSTEM=$(nix-build ${data.external.nixos_system.result.drv} --no-out-link)", + "sudo nix-env --profile /nix/var/nix/profiles/system --set $SYSTEM", + "sudo $SYSTEM/bin/switch-to-configuration switch", + ] + } + + triggers = merge({ + nixos_drv = data.external.nixos_system.result.drv + target_host = var.target_host + }, var.triggers) +} + +output "nixos_drv" { + value = data.external.nixos_system.result +} diff --git a/ops/terraform/deploy-nixos/nix-eval.sh b/ops/terraform/deploy-nixos/nix-eval.sh new file mode 100755 index 0000000000..65f534180b --- /dev/null +++ b/ops/terraform/deploy-nixos/nix-eval.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2023 The TVL Authors +# +# SPDX-License-Identifier: MIT +set -ueo pipefail + +# Evaluates a Nix expression. +# +# Receives input parameters as JSON from stdin. +# It expects a dict with the following keys: +# +# - `attrpath`: the attribute.path pointing to the expression to instantiate. +# Required. +# - `entrypoint`: the path to the Nix file to invoke. +# Optional. If omitted, will shell out to git to determine the repo root, +# and Nix will use `default.nix` in there. +# - `argstr_json`: A string JSON-encoding a map containing string keys and +# values which should be passed to Nix as `--argstr $key $value`. +# command line args. Optional. +# - `build`: A boolean (or string being "true" or "false") stating whether the +# expression should also be built/substituted on the machine executing this script. +# +# jq's @sh format takes care of escaping. +eval "$(jq -r '@sh "attrpath=\(.attrpath) && entrypoint=\(.entrypoint) && argstr=\((.argstr_json // "{}"|fromjson) | to_entries | map ("--argstr", .key, .value) | join(" ")) build=\(.build)"')" + +# Evaluate the expression. +[[ -z "$entrypoint" ]] && entrypoint=$(git rev-parse --show-toplevel) +# shellcheck disable=SC2086,SC2154 +drv=$(nix-instantiate -A "${attrpath}" "${entrypoint}" ${argstr}) + +# If `build` is set to true, invoke nix-build on the .drv. +# We need to swallow all stdout, to not garble the JSON printed later. +# shellcheck disable=SC2154 +if [ "${build}" == "true" ]; then + nix-build --no-out-link "${drv}" > /dev/null +fi + +# Determine the output path. +outPath=$(nix show-derivation "${drv}" | jq -r ".\"${drv}\".outputs.out.path") + +# Return a JSON back to stdout. +# It contains the following keys: +# +# - `drv`: the store path of the Derivation that has been instantiated. +# - `outPath`: the output store path. +jq -n --arg drv "$drv" --arg outPath "$outPath" '{"drv":$drv, "outPath":$outPath}' diff --git a/ops/terraform/deploy-nixos/nixos-copy.sh b/ops/terraform/deploy-nixos/nixos-copy.sh new file mode 100755 index 0000000000..6b843c3a49 --- /dev/null +++ b/ops/terraform/deploy-nixos/nixos-copy.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2023 The TVL Authors +# +# SPDX-License-Identifier: MIT + +# +# Copies a NixOS system to a target host, using the provided key, +# or whatever ambient key is configured if the key is not set. +set -ueo pipefail + +export NIX_SSHOPTS="\ + -o StrictHostKeyChecking=no\ + -o UserKnownHostsFile=/dev/null\ + -o GlobalKnownHostsFile=/dev/null" + +# If DEPLOY_KEY was passed, write it to $scratch/id_deploy +if [ -n "${DEPLOY_KEY-}" ]; then + scratch="$(mktemp -d)" + trap 'rm -rf -- "${scratch}"' EXIT + + echo -n "$DEPLOY_KEY" > $scratch/id_deploy + chmod 0600 $scratch/id_deploy + export NIX_SSHOPTS="$NIX_SSHOPTS -o IdentityFile=$scratch/id_deploy" +fi + +nix-copy-closure \ + --to ${TARGET_USER}@${TARGET_HOST} \ + ${SYSTEM_DRV} \ + --gzip \ + --include-outputs \ + --use-substitutes diff --git a/ops/users/default.nix b/ops/users/default.nix new file mode 100644 index 0000000000..34e0ab85c3 --- /dev/null +++ b/ops/users/default.nix @@ -0,0 +1,227 @@ +{ ... }: + +[ + { + username = "aaqaishtyaq"; + email = "aaqaishtyaq@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IpWJeEYTYEsrgGBNQcnbWA$w4+gQmeJlhddeaHvmbpNa3hDVg1BkJESZSVAd2eSOs4"; + } + { + username = "adisbladis"; + email = "adisbladis@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wdgoLRrUgZuz0Kin9YiNgQ$E40VIgzgpMpylZqkfByTKiWQnerupfuf7LDgOsU8tJA"; + } + { + username = "andi"; + email = "andi@notmuch.email"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$8lefg7+8UPAEh9Ott8zH0A$7YuLRraTC1IgxTNTxFJF03AWmqBS3GX2+vfD4XVTrb0"; + } + { + username = "aspen"; + email = "root@gws.fyi"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$5NEYPJ19nDITK5sGr4bzhQ$Xzpzth6y4w+HGvioHiYgzqFiwMDx0B7HAh+PVbkRuuk"; + } + { + username = "cschilling"; + email = "christian.schilling.de@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$9VN3IS6ViW5FFbVKWOZI6Q$gZxuYAYk0Opq4E5i8cbcNjfznCQNc+RiP7Xv1CUnrQU"; + } + { + username = "cynthia"; + email = "me@cynthia.re"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=4,p=1$TxjbMGenhEmkyYLrg5uGhbr60THB86YeRZg5bPdiTJo$k9gbRlAPjmxwdUwzbavvsAVkckgQZ0jS2oTtvZBPysk"; + } + { + username = "edef"; + email = "edef@edef.eu"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OORx4ERbkgvTmuYCJA8cIw$i5qaBzHkRVw7Tl+wZsTFTDqJwF0vuZqhW3VpknMYMc0"; + } + { + username = "ericvolp12"; + email = "ericvolp12@gmail.com"; + password = "{SSHA}pSepaQ+/5KBLfJtRR5rfxGU8goAsXgvk"; + } + { + username = "eta"; + email = "tvl@eta.st"; + password = "{SSHA}sOR5xzi7Lfv376XGQA8Hf6jyhTvo0XYc"; + } + { + username = "etu"; + email = "etu@failar.nu"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$RUrW8C9mWAkBSlkwSTH5dw$n3FXTeu41nDQfvJPI7TT3tcgwPmPJl8hPtaZ58qLq9A"; + } + { + username = "firefly"; + email = "firefly@firefly.nu"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$RYVVkFoi3A1yYkI8J2zUwg$GUERvgHvU8SGjQmilDJGZu50hYRAHw+ejtuL+Skygs8"; + } + { + username = "flokli"; + email = "flokli@flokli.de"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$TrezbwIY5TKLnJiii0wafQ$K0S2p9I8tiqP907nkgoK6IbG9ia4IuDiylTcIs5pesw"; + } + { + username = "ghuntley"; + email = "ghuntley@ghuntley.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$ciCuQHeA7csqrFUv7+asgw$7GUC5fLJWWVoHP8DvpA+C1u4+iFdV2E311kwTFwGzaQ"; + } + { + username = "htbf"; + email = "h-tvl@htbf.dev"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$2iVXQQfd26icaIguHJg/CQ$hA9ziqn7kQ06AV6uQxJCGXoG8f+LWmH+nVlk00a1n/c"; + } + { + username = "IslandUsurper"; + email = "lyle@menteeth.us"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$rNSsa8aYU4qvxeFnADgW1g$Zu6B6Al2usRRNfAKhWXzCAfiTfV3XQb0W6Op5TYN1oI"; + } + { + username = "isomer"; + email = "isomer@tvl.fyi"; + password = "{SSHA}OhWQkPJgH1rRJqYIaMUbbKC4iLEzvCev"; + } + { + username = "j4m3s"; + email = "james.landrein@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$dMYmo+Uym9irtzAGXB2eNw$69OFcuqCqoLPBXKmmtYaQCquXximpyxsb2Kf8U7GdxM"; + } + { + username = "jfroche"; + email = "jfroche@pyxel.be"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$kA19gDabD1Fjy82olcmnsA$TTbkpAc0WYaA4DT2vc7+NAGXhC4Os1tPqZVpHFkzecE"; + } + { + username = "jrhahn"; + email = "mail.jhahn@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$giiu99hS7CzfsDZgxMNvKg$JiZZnFxOGHZRlUziYd3TkEiUplMz7Emy8fXfyLawPS0"; + } + { + username = "kn"; + email = "klemens@posteo.de"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$CoRZInysud4sduDoMjVOCw$/bdvAvyPO2DPxOcHlBiG2+rbTGF9XAcHUhPurxiIpZM"; + } + { + username = "lukegb"; + email = "lukegb@tvl.fyi"; + password = "{SSHA}7a85VNhpFElFw+N5xcjgGmt4HnBsaGp4"; + } + { + username = "noteed"; + email = "noteed@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$rcLfF9xXysSx5sahVQLiMA$EgRgAVXn8+r2Csa3XgIHIEBf3hX4Y58pOHf2eDaBUnA"; + } + { + username = "nyanotech"; + email = "nyanotechnology@gmail.com"; + password = "{SSHA}NIJ2RCRb1+Q4Bs63cyE91VZyiN47DG6y"; + } + { + username = "Profpatsch"; + email = "mail@profpatsch.de"; + password = "{SSHA}jcFXxRplMFxH4gpa0X5VdUzW64T95TwQ"; + } + { + username = "sterni"; + email = "sternenseemann@systemli.org"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$+NbF1izPMGqN5bASCBDV9g$aqBVplHwiyDpflZUmLtjkLWzKhxi7hwjm5fOwfbKohU"; + } + { + username = "qyliss"; + displayName = "Alyssa Ross"; + email = "hi@alyssa.is"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$+uTpAKrN452D8wa7OFqPnw$GYi9/zns5iJCXDp1VuTPPsa35M5vkD6+rC8riT8cEHI"; + } + { + username = "riking"; + displayName = "kanepyork"; + email = "rikingcoding@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$o2OcfhfKOry+UrcmODyQCw$qloaQgoIRDESwaA3yqPxxy8sgLk3mrjYFBbF41elVrM"; + } + { + username = "talyz"; + email = "kim.lindberger@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$KYgHYsxX/DZDhnxdkzn1/w$L2Yyc2lYAREZP0FD3iX57MB6gzoOCcVmCGDxIsUGAgk"; + } + { + username = "tazjin"; + email = "tazjin@tvl.su"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wOPEl9D3kSke//oLtbvqrg$j0npwwXgaXQ/emefKUwL59tH8hdmtzbgH2rQzWSmE2Y"; + } + { + username = "implr"; + email = "implr@hackerspace.pl"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$SHRFps5sVgyUXYdmqGPw9g$tEx9DwKK1RjWlw52GLwOZ/iHep+QJboaZE83f1pXSwQ"; + } + { + username = "ben"; + email = "tvl@benjojo.co.uk"; + password = "{SSHA}Zi48mSPsRMEPhff44w4RHi0SjjyhjWk1"; + } + { + username = "jamie"; + email = "jamie@kwiius.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$OkAMHVAfQ3nJhBffYJwk7Q$JV3DrF9eOU+4VL6I+nkaMUUOMqWuNzdp7N7U5Xwa3fg"; + } + { + username = "milan"; + email = "milan@petabyte.dev"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$VQAHgOqYVr7mzjEr8IAMdQ$eAXvy58eRkjg+96RKBCwUoRDpNyGDdes4rVtxoQbaeI"; + } + { + username = "ezemtsov"; + email = "eugene.zemtsov@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$eAEjTm0+JD0+p0o/GA8JmQ$uRtHCT+B/DBNr1rlOcLlROrkYsMj2T9ns/E9Ep3gJ1A"; + } + { + username = "mdjnsn"; + email = "mdj@mikejohnson.xyz"; + displayName = "Mike Johnson"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$77+f5DFbuzs5myyIUN2nHg$xyXkRhIHFVaPMZUhxPk1uxMpLeEmU3BeyQjDsNPlJVw"; + } + { + username = "smitop"; + email = "me@smitop.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$H78rQtmhlzrPEifbXPoCVw$IBg7ePTm/u+e8r2A8aJ4iaaQBzMUw1isS9YJAZ8aT3o"; + } + { + username = "asmundo"; + email = "asmundo@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$oQvNAAGjshz5Cl8XW5i31A$eurlL9a7e5Ttw5JpTY9tOjSZyivWQsr1iCdTqshdfQU"; + } + { + username = "wpcarro"; + email = "wpcarro@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$NQdBVPNwh2ioDq9zWfMusA$2cABJGI8cU2JZirnVU5E5C28sTiePkiOPEAaqNUp/Fk"; + } + { + username = "fogti"; + email = "fogti+devel@ytrizja.de"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$wVNkImXloXIkCycnecdFeA$ECAdGdNzUUEq9sFGsIl0jb7AALGsHE+ndWRn6ilSmdE"; + } + { + username = "brainrake"; + email = "martonboros@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$f4/ewdyRBQbClL4KzqypHg$6Ql/xkmfIr60Qp1XMaFherqhh4cekLIbsi7KMM6izfE"; + } + { + username = "raitobezarius"; + email = "tvl@lahfa.xyz"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$3NZTBbF5dZssAHC/ktcA/Q$AZxHGG0ycNMOkIxC/ONYbyhNxC9hb6cpWvnsNH8LWZk"; + } + { + username = "hsjobeki"; + email = "hsjobeki@gmail.com"; + password = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$jez9eVa2v0BznIJMOhw+hw$wUbwCS+Bfcjjzr08saQE6NNTPWNXWWaxv+UtBCdYC2s"; + } + { + username = "totikom"; + email = "eugene.lomov@protonmail.com"; + password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$r/EsEGkqCcv8ccjQ84pX7Q$ebpWno7LI1RXkWKBjnkDHZM1gPuPj1LSMoFUsX0j6AU"; + } + { + username = "espes"; + email = "espes@pequalsnp.com"; + password = "{ARGON2}$argon2id$v=19$m=19456,t=2,p=1$eXeFrbNxuKn/JCpQr5VmxA$NtMNBceNg/JtqMfHk/qHxEHsEVsTWmHJbpq4ve/+XYg"; + } +] diff --git a/ops/yandex-base-image/default.nix b/ops/yandex-base-image/default.nix new file mode 100644 index 0000000000..3dc4b8f589 --- /dev/null +++ b/ops/yandex-base-image/default.nix @@ -0,0 +1,9 @@ +# Base image for Yandex Cloud VMs. +{ depot, ... }: + +(depot.ops.nixos.nixosFor { + imports = [ + (depot.path.origSrc + ("/ops/modules/yandex-cloud.nix")) + (depot.path.origSrc + ("/ops/modules/tvl-users.nix")) + ]; +}).config.system.build.yandexCloudImage diff --git a/ops/yandex-cloud-rs/.gitignore b/ops/yandex-cloud-rs/.gitignore new file mode 100644 index 0000000000..ab3f21a96e --- /dev/null +++ b/ops/yandex-cloud-rs/.gitignore @@ -0,0 +1,5 @@ +target/ +result/ +# Ignore everything under src (except for lib.rs) +src/* +!src/lib.rs diff --git a/ops/yandex-cloud-rs/Cargo.lock b/ops/yandex-cloud-rs/Cargo.lock new file mode 100644 index 0000000000..0015d43106 --- /dev/null +++ b/ops/yandex-cloud-rs/Cargo.lock @@ -0,0 +1,1368 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "flate2", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs", + "rustls-pemfile", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "yandex-cloud" +version = "2023.9.4" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tonic", + "tonic-build", + "walkdir", +] diff --git a/ops/yandex-cloud-rs/Cargo.toml b/ops/yandex-cloud-rs/Cargo.toml new file mode 100644 index 0000000000..a72d11d59a --- /dev/null +++ b/ops/yandex-cloud-rs/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "yandex-cloud" +description = "Generated gRPC clients for the Yandex Cloud API" +license = "MIT" +version = "2023.9.4" +edition = "2021" +homepage = "https://cs.tvl.fyi/depot/-/tree/ops/yandex-cloud-rs" +repository = "https://code.tvl.fyi/depot.git:/ops/yandex-cloud-rs.git" +include = [ "/src", "README.md" ] + +[dependencies] +prost = "0.11" +prost-types = "0.11" + +[dependencies.tonic] +version = "0.9" +features = [ "tls", "tls-roots", "gzip" ] + +[build-dependencies] +tonic-build = "0.9" +walkdir = "2.3.3" + +[dev-dependencies] +tokio = "1.28" # check when updating tonic diff --git a/ops/yandex-cloud-rs/README.md b/ops/yandex-cloud-rs/README.md new file mode 100644 index 0000000000..a80fa83163 --- /dev/null +++ b/ops/yandex-cloud-rs/README.md @@ -0,0 +1,49 @@ +yandex-cloud-rs +=============== + +Client library for Yandex Cloud gRPC APIs, as published in their +[GitHub repository][repo]. + +Please see the [online documentation][docs] for user-facing +information, this README is intended for library developers. + +The source code of the library lives [in the TVL repository][code]. + +------------- + +In order to build this library, the gRPC API definitions need to be +fetched from GitHub. By default this is done by Nix (see +`default.nix`), which then injects the location of the API definitions +through the `YANDEX_CLOUD_PROTOS` environment variable. + +The actual code generation happens through the calls in `build.rs`. + +Releases of this library are done from *dirty* trees, meaning that the +version on crates.io should already contain all the generated code. In +order to do this, after bumping the version in `Cargo.toml` and the +API commit in `default.nix`, the following release procedure should be +used: + +``` +# Get rid of all generated source files +find src | grep '.rs$' | grep -v '^src/lib.rs$' | xargs rm + +# Get rid of all old artefacts +cargo clean + +# Verify that a clean build works as intended +cargo build + +# Verify that all documentation builds, and verify that it looks fine: +# +# - Is the version correct (current date)? +# - Are all the services included (i.e. not an accidental empty build)? +cargo doc --open + +# If everything looks fine, release: +cargo publish --allow-dirty +``` + +[repo]: https://github.com/yandex-cloud/cloudapi +[docs]: https://docs.rs/yandex-cloud/latest/yandex_cloud/ +[code]: https://cs.tvl.fyi/depot/-/tree/ops/yandex-cloud-rs diff --git a/ops/yandex-cloud-rs/build.rs b/ops/yandex-cloud-rs/build.rs new file mode 100644 index 0000000000..e9a96ef9df --- /dev/null +++ b/ops/yandex-cloud-rs/build.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; +use walkdir::{DirEntry, WalkDir}; + +fn proto_files(proto_dir: &str) -> Vec<PathBuf> { + let mut out = vec![]; + + fn is_proto(entry: &DirEntry) -> bool { + entry.file_type().is_file() + && entry + .path() + .extension() + .map(|e| e.to_string_lossy() == "proto") + .unwrap_or(false) + } + + for entry in WalkDir::new(format!("{}/yandex", proto_dir)).into_iter() { + let entry = entry.expect("failed to list proto files"); + + if is_proto(&entry) { + out.push(entry.into_path()) + } + } + + out +} + +fn main() { + if let Some(proto_dir) = option_env!("YANDEX_CLOUD_PROTOS") { + tonic_build::configure() + .build_client(true) + .build_server(false) + .out_dir("src/") + .include_file("includes.rs") + .compile( + &proto_files(proto_dir), + &[ + format!("{}", proto_dir), + format!("{}/third_party/googleapis", proto_dir), + ], + ) + .expect("failed to generate gRPC clients for Yandex Cloud") + } +} diff --git a/ops/yandex-cloud-rs/default.nix b/ops/yandex-cloud-rs/default.nix new file mode 100644 index 0000000000..6a8b263dee --- /dev/null +++ b/ops/yandex-cloud-rs/default.nix @@ -0,0 +1,22 @@ +{ depot, lib, pkgs, ... }: + +let + protoSrc = pkgs.fetchFromGitHub { + owner = "yandex-cloud"; + repo = "cloudapi"; + rev = "b4383be5ebe360bd946e49c8eaf647a73e9c44c0"; + sha256 = "0z4jyw2cylvyrq5ja8pcaqnlf6lf6ximj85hgjag6ckawayk1rzx"; + }; +in +pkgs.rustPlatform.buildRustPackage rec { + name = "yandex-cloud-rs"; + src = depot.third_party.gitignoreSource ./.; + cargoLock.lockFile = ./Cargo.lock; + YANDEX_CLOUD_PROTOS = "${protoSrc}"; + nativeBuildInputs = [ pkgs.protobuf ]; + + # The generated doc comments contain lots of things that rustc + # *thinks* are doctests, but are actually just garbage leading to + # compiler errors. + doCheck = false; +} diff --git a/ops/yandex-cloud-rs/examples/log-write.rs b/ops/yandex-cloud-rs/examples/log-write.rs new file mode 100644 index 0000000000..84d183421a --- /dev/null +++ b/ops/yandex-cloud-rs/examples/log-write.rs @@ -0,0 +1,37 @@ +//! This example uses the Yandex Cloud Logging API to write a log entry. + +use prost_types::Timestamp; +use tonic::transport::channel::Endpoint; +use yandex_cloud::yandex::cloud::logging::v1::destination::Destination; +use yandex_cloud::yandex::cloud::logging::v1::log_ingestion_service_client::LogIngestionServiceClient; +use yandex_cloud::yandex::cloud::logging::v1::Destination as OuterDestination; +use yandex_cloud::yandex::cloud::logging::v1::IncomingLogEntry; +use yandex_cloud::yandex::cloud::logging::v1::WriteRequest; +use yandex_cloud::AuthInterceptor; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let channel = Endpoint::from_static("https://ingester.logging.yandexcloud.net") + .connect() + .await?; + + let mut client = LogIngestionServiceClient::with_interceptor( + channel, + AuthInterceptor::new("YOUR_TOKEN_HERE"), + ); + + let request = WriteRequest { + destination: Some(OuterDestination { + destination: Some(Destination::LogGroupId("YOUR_LOG_GROUP_ID".into())), + }), + entries: vec![IncomingLogEntry { + timestamp: Some(Timestamp::date_time(2023, 04, 24, 23, 44, 30).unwrap()), + message: "test log message".into(), + ..Default::default() + }], + ..Default::default() + }; + + client.write(request).await.unwrap(); + Ok(()) +} diff --git a/ops/yandex-cloud-rs/src/lib.rs b/ops/yandex-cloud-rs/src/lib.rs new file mode 100644 index 0000000000..e7f79c75be --- /dev/null +++ b/ops/yandex-cloud-rs/src/lib.rs @@ -0,0 +1,108 @@ +//! This module provides low-level generated gRPC clients for the +//! Yandex Cloud APIs. +//! +//! The clients are generated using the [tonic][] and [prost][] +//! crates and have default configuration. +//! +//! Documentation present in the protos is retained into the generated +//! Rust types, but for detailed API information you should visit the +//! official Yandex Cloud Documentation pages: +//! +//! * [in English](https://cloud.yandex.com/en-ru/docs/overview/api) +//! * [in Russian](https://cloud.yandex.ru/docs/overview/api) +//! +//! The proto sources are available on the [Yandex Cloud GitHub][protos]. +//! +//! [tonic]: https://docs.rs/tonic/latest/tonic/ +//! [prost]: https://docs.rs/prost/latest/prost/ +//! [protos]: https://github.com/yandex-cloud/cloudapi +//! +//! The majority of user-facing structures can be found in the +//! [`yandex::cloud`] module. +//! +//! ## Usage +//! +//! Typically to use these APIs, you need to provide an authentication +//! credential and an endpoint to connect to. The full list of +//! Yandex's endpoints is [available online][endpoints] and you should +//! look up the service you plan to use and pick the correct endpoint +//! from the list. +//! +//! Authentication is done via an HTTP header using an IAM token, +//! which can be done in Tonic using [interceptors][]. The +//! [`AuthInterceptor`] provided by this crate can be used for that +//! purpose. +//! +//! Full usage examples are [available here][examples]. +//! +//! [endpoints]: https://cloud.yandex.com/en/docs/api-design-guide/concepts/endpoints +//! [interceptors]: https://docs.rs/tonic/latest/tonic/service/trait.Interceptor.html +//! [examples]: https://code.tvl.fyi/tree/ops/yandex-cloud-rs/examples + +use tonic::metadata::{Ascii, MetadataValue}; +use tonic::service::Interceptor; + +/// Publicly re-export some types from tonic which users might need +/// for implementing traits, or for naming concrete client types. +pub mod tonic_exports { + pub use tonic::service::interceptor::InterceptedService; + pub use tonic::transport::Channel; + pub use tonic::transport::Endpoint; + pub use tonic::Status; +} + +/// Helper trait for types or closures that can provide authentication +/// tokens for Yandex Cloud. +pub trait TokenProvider { + /// Fetch a currently valid authentication token for Yandex Cloud. + fn get_token<'a>(&'a mut self) -> Result<&'a str, tonic::Status>; +} + +impl TokenProvider for String { + fn get_token<'a>(&'a mut self) -> Result<&'a str, tonic::Status> { + Ok(self.as_str()) + } +} + +impl TokenProvider for &'static str { + fn get_token(&mut self) -> Result<&'static str, tonic::Status> { + Ok(*self) + } +} + +/// Interceptor for adding authentication headers to gRPC requests. +/// This is constructed with a callable that returns authentication +/// tokens. +/// +/// This callable is responsible for ensuring that the returned tokens +/// are valid at the given time, i.e. it should take care of +/// refreshing and so on. +pub struct AuthInterceptor<T: TokenProvider> { + token_provider: T, +} + +impl<T: TokenProvider> AuthInterceptor<T> { + pub fn new(token_provider: T) -> Self { + Self { token_provider } + } +} + +impl<T: TokenProvider> Interceptor for AuthInterceptor<T> { + fn call( + &mut self, + mut request: tonic::Request<()>, + ) -> Result<tonic::Request<()>, tonic::Status> { + let token: MetadataValue<Ascii> = format!("Bearer {}", self.token_provider.get_token()?) + .try_into() + .map_err(|_| { + tonic::Status::invalid_argument("authorization token contained invalid characters") + })?; + + request.metadata_mut().insert("authorization", token); + + Ok(request) + } +} + +// The rest of this file is generated by the build script at ../build.rs. +include!("includes.rs"); |