-
Notifications
You must be signed in to change notification settings - Fork 0
feat: parse PEM bundle in GenerateCertificate and add --private-key flag #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Move PEM bundle parsing into GenerateCertificate to reduce duplicate work - Identify cert vs CA by checking x509 IsCA field (order-independent) - Identify private key by PEM block type (PRIVATE KEY, RSA/EC PRIVATE KEY) - Add --private-key flag with support for PEM string, base64, or file path - Add loadPrivateKeyPEM helper for flexible key input handling
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds ECDSA private-key input (PEM/file/base64) and deprecates the token flag; moves TLS certificate provisioning into a provider onConnect callback that manages proxy server lifecycle with mutexed swaps; updates AgentMetadata and CreateNetworkProvider signatures; adds PEM bundle parsing and Gravity CA selection; bumps module/tooling versions. Changes
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
✏️ Tip: You can disable this entire section by setting Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/stack/gravityClient.go (1)
15-27: Guard against nil onConnect to avoid a panic.If GravityClient is instantiated without onConnect, Configure will nil-deref. Add a nil check or default no-op handler.
🐛 Proposed fix
func (p *GravityClient) Configure(config provider.Configuration) error { p.logger.Debug("configuring provider") p.config = config p.Connected <- struct{}{} - return p.onConnect(&config) + if p.onConnect == nil { + return nil + } + return p.onConnect(&config) }cmd/start.go (1)
65-88: Add support for PKCS#8 EC keys (BEGIN PRIVATE KEY) to prevent failures with valid keys.The current code uses only
x509.ParseECPrivateKey, which handles SEC1 EC keys (PEM label "EC PRIVATE KEY") but fails on PKCS#8 keys (PEM label "PRIVATE KEY"), even though both are valid EC private key formats. Useblock.Typeto distinguish and handle both formats:Suggested fix
import ( "context" + "crypto/ecdsa" "crypto/x509" "encoding/base64" "encoding/pem" @@ block, _ := pem.Decode(privateKeyPEM) if block == nil { logger.Fatal("no PEM block found in --private-key") } - privateKey, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - logger.Fatal("failed to parse private key: %v", err) - } + var privateKey *ecdsa.PrivateKey + switch block.Type { + case "EC PRIVATE KEY": + privateKey, err = x509.ParseECPrivateKey(block.Bytes) + case "PRIVATE KEY": + var k interface{} + k, err = x509.ParsePKCS8PrivateKey(block.Bytes) + if err == nil { + var ok bool + privateKey, ok = k.(*ecdsa.PrivateKey) + if !ok { + err = fmt.Errorf("private key is not ECDSA") + } + } + default: + err = fmt.Errorf("unsupported private key type: %s", block.Type) + } + if err != nil { + logger.Fatal("failed to parse private key: %v", err) + }
🤖 Fix all issues with AI agents
In `@cmd/start.go`:
- Around line 118-135: The shutdown call can dereference a nil server if the
server was never started; ensure every place that calls server.Shutdown first
checks server != nil. In the anonymous onConnect callback passed into
stack.CreateNetworkProvider (func(c *provider.Configuration) error) and in any
later cleanup/shutdown path that uses the captured server variable, wrap the
Shutdown call with a nil guard (if server != nil { if err :=
server.Shutdown(ctx); err != nil { logger.Error(...) } server = nil }). This
uses the existing server variable, provider.Configuration callback, and
stack.StartServer/stack.GenerateCertificate symbols to locate and fix the spots.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (4)
cmd/start.gogo.modinternal/stack/gravityClient.gointernal/stack/stack.go
🧰 Additional context used
🧬 Code graph analysis (2)
cmd/start.go (1)
internal/stack/stack.go (2)
AgentMetadata(38-43)CreateNetworkProvider(301-385)
internal/stack/stack.go (1)
internal/stack/gravityClient.go (1)
GravityClient(15-21)
🔇 Additional comments (9)
internal/stack/stack.go (5)
3-22: Import updates align with the new key/provider wiring.
38-43: PrivateKey addition in AgentMetadata fits the new auth flow.
87-163: Bundle parsing and config assembly look coherent.
301-372: onConnect wiring and client config updates look consistent.
403-454: PEM bundle parsing logic is clear and defensive.cmd/start.go (3)
3-19: Import additions look aligned with the new private-key and server flow.
234-239: Required private-key flag addition is good.
241-276: Input handling in the key-loading helper is clean and readable.go.mod (1)
3-6: Verify go-common v1.0.142 availability and compatibility with current APIs.Go 1.25.5 was released December 2, 2025 and is available in standard toolchains. However, go-common v1.0.142 is an internal module; verify this version is published in your module proxy/registry and confirm compatibility with any API changes in your codebase.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@cmd/start.go`:
- Around line 127-143: The server variable is accessed concurrently
(created/assigned in the provider callback and read/closed in shutdown), causing
a data race; introduce a sync.Mutex (or sync.RWMutex) next to the server
declaration and use it to guard all reads/writes to server inside the
CreateNetworkProvider callback (the anonymous func(c *provider.Configuration)
error) and the shutdown path where server is checked/Shutdown/cleared,
performing a safe swap: lock, capture current server into local, set shared
server to nil, unlock, then perform Shutdown on the captured instance; apply the
same mutex protection to the other server access site referenced in the review.
- Around line 76-91: The error log currently prints the raw privateKeyPEM on
parse failure (in the block handling x509.ParsePKCS8PrivateKey /
x509.ParseECPrivateKey), leaking secrets; modify the failure path to remove the
privateKeyPEM from the logger.Fatal call and instead log a non-sensitive
indicator (e.g., error only, or a fingerprint/length) when
loadPrivateKeyPEM/privateKeyPEM parsing fails; update the logger.Fatal
invocation(s) near x509.ParsePKCS8PrivateKey / x509.ParseECPrivateKey to exclude
string(privateKeyPEM) and optionally include only safe metadata such as
len(privateKeyPEM) or a hash digest.
🧹 Nitpick comments (3)
internal/stack/certs.go (1)
49-69: Optional: accept URLs without a scheme to avoid false prod selection.
If callers pass a host without a scheme (e.g.,devmode.agentuity.com:443),url.Parseyields an empty hostname and you’ll default to prod CA. A lightweight fallback keeps this resilient.♻️ Suggested tweak
- parsedURL, err := url.Parse(apiURL) - if err != nil { - return false - } + parsedURL, err := url.Parse(apiURL) + if err == nil && parsedURL.Hostname() == "" { + parsedURL, err = url.Parse("https://" + apiURL) + } + if err != nil { + return false + }cmd/start.go (1)
257-288: Optional: surface file read errors before base64 fallback.
Ifos.ReadFilefails due to permissions or IO errors, the current path treats the input as base64 and loses the real cause.♻️ Suggested tweak
- // Check if it's a file path - if data, err := os.ReadFile(input); err == nil { + // Check if it's a file path + if data, err := os.ReadFile(input); err == nil { if strings.HasPrefix(strings.TrimSpace(string(data)), "-----BEGIN") { return data, nil } // File contains base64 decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data))) if err == nil && strings.HasPrefix(string(decoded), "-----BEGIN") { return decoded, nil } return nil, fmt.Errorf("file does not contain valid PEM or base64-encoded PEM") + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("failed to read private key file: %w", err) }internal/stack/stack.go (1)
87-129: Optional: decode CA PEM before parsing for logging.
x509.ParseCertificateexpects DER bytes; decoding the PEM first avoids the guaranteed failure and simplifies the logging block.♻️ Suggested tweak
- // Parse and log CA certificate details - caCert, err := x509.ParseCertificate(caCertPEM) - if err == nil { - logger.Debug("Loaded CA certificate: CN=%s, Issuer=%s", caCert.Subject.CommonName, caCert.Issuer.CommonName) - } else { - // Try parsing as PEM - block, _ := pem.Decode(caCertPEM) - if block != nil { - caCert, err = x509.ParseCertificate(block.Bytes) - if err == nil { - logger.Debug("Loaded CA certificate (PEM): CN=%s, Issuer=%s", caCert.Subject.CommonName, caCert.Issuer.CommonName) - } - } - } + // Parse and log CA certificate details + if block, _ := pem.Decode(caCertPEM); block != nil { + if caCert, err := x509.ParseCertificate(block.Bytes); err == nil { + logger.Debug("Loaded CA certificate: CN=%s, Issuer=%s", caCert.Subject.CommonName, caCert.Issuer.CommonName) + } else { + logger.Warn("Failed to parse CA certificate for logging: %v", err) + } + }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cmd/start.gointernal/stack/certs.gointernal/stack/stack.go
🧰 Additional context used
🧬 Code graph analysis (2)
cmd/start.go (1)
internal/stack/stack.go (2)
AgentMetadata(38-43)CreateNetworkProvider(301-386)
internal/stack/stack.go (2)
internal/stack/gravityClient.go (1)
GravityClient(15-21)internal/stack/certs.go (1)
GravityCACertificate(38-47)
🔇 Additional comments (6)
internal/stack/certs.go (2)
10-35: CA PEM constants look good.
Embedded prod/dev CA PEMs are fine here.
37-47: LGTM for environment-based CA selection.
Clear, small, and easy to follow.cmd/start.go (1)
246-250: Required--private-keyflag wiring looks good.
Clear CLI contract and help text.internal/stack/stack.go (3)
38-43: AgentMetadata private key field addition looks good.
Matches the new auth flow.
301-355: Provider wiring with onConnect + ECDSA key looks consistent.
The new config surface aligns with the updated Gravity client flow.
404-455: PEM bundle parsing and CA detection looks solid.
The block-type scan + IsCA detection is straightforward and robust for the two-cert bundle assumption.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@cmd/start.go`:
- Around line 67-75: Change the unclear deprecation log for the token flag: when
reading token via flags.GetString("token") and detecting token != "", update the
logger.Fatal message (the code around maybePrivateKey and flags) to explicitly
instruct users to stop using --token/AGENTUITY_SDK_KEY and to use --private-key
(or the private key env/flag) instead — reference the token variable and the
logger.Fatal call and replace the generic "update your Agentuity CLI" text with
a clear, actionable message that says remove --token/AGENTUITY_SDK_KEY and
supply the private key via --private-key.
- Around line 82-109: The current logic decodes only the first PEM block from
privateKeyPEM and fails if that block isn't a key; update the parsing near
pem.Decode(privateKeyPEM) to iterate with pem.Decode in a loop over the
remaining bytes and select a block whose block.Type contains "PRIVATE KEY" (or
ends with "PRIVATE KEY") before attempting x509.ParsePKCS8PrivateKey /
x509.ParseECPrivateKey; for each candidate block try PKCS#8 then EC parsing (as
the existing fallback does) and only break/assign privateKey when parsing
succeeds, otherwise continue to the next PEM block; if no private-key block
parses successfully, call logger.Fatal with an informative message (include
len(privateKeyPEM) as currently done).
- Around line 133-155: The shutdown of oldServer in the provider reconfigure
callback currently calls oldServer.Shutdown(ctx) with the long-lived root
context and can block indefinitely; change that call to use a bounded timeout
context (e.g., via context.WithTimeout(parentCtx, timeout)) so the shutdown
completes or aborts within a fixed grace period, ensure you call cancel() and
handle/log Shutdown errors including context deadline exceeded, and keep the
rest of the callback (GenerateCertificate, StartServer, and assignment to server
under serverMu) unchanged; update the closure where oldServer is set and
Shutdown is invoked (the variables server, serverMu, oldServer in the
CreateNetworkProvider callback).
🧹 Nitpick comments (1)
cmd/start.go (1)
269-305: Consider accepting unpadded base64 for better ergonomics.
Some base64 encoders omit padding; falling back toRawStdEncodingimproves interoperability. This is a nice-to-have robustness tweak.♻️ Optional base64 fallback
- decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data))) + cleaned := strings.TrimSpace(string(data)) + decoded, err := base64.StdEncoding.DecodeString(cleaned) + if err != nil { + decoded, err = base64.RawStdEncoding.DecodeString(cleaned) + } if err == nil && strings.HasPrefix(string(decoded), "-----BEGIN") { return decoded, nil } @@ - decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(input)) + cleaned := strings.TrimSpace(input) + decoded, err := base64.StdEncoding.DecodeString(cleaned) + if err != nil { + decoded, err = base64.RawStdEncoding.DecodeString(cleaned) + } if err != nil { return nil, fmt.Errorf("input is not valid PEM, base64, or file path: %w", err) }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
cmd/start.go
🧰 Additional context used
🧬 Code graph analysis (1)
cmd/start.go (2)
internal/utils/utils.go (1)
GetPrivateIPv4(8-20)internal/stack/stack.go (4)
AgentMetadata(38-43)CreateNetworkProvider(301-386)GenerateCertificate(87-163)StartServer(165-238)
🔇 Additional comments (3)
cmd/start.go (3)
5-19: No review note for the import additions.
210-217: No additional notes on the shutdown block.
262-267: No review note for the new flag wiring.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| maybePrivateKey, _ := flags.GetString("private-key") | ||
| endpointID, _ := flags.GetString("endpoint-id") | ||
| gravityUrl, _ := flags.GetString("url") | ||
| healthCheck, _ := flags.GetBool("health-check") | ||
| token, _ := flags.GetString("token") | ||
|
|
||
| if token != "" { | ||
| logger.Fatal("The --token flag is no longer supported. Please update your Agentuity CLI to the latest version.") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the --token deprecation message actionable.
The current fatal message implies “update CLI,” but the fix is to remove --token/AGENTUITY_SDK_KEY and use --private-key. Consider updating the text to reduce user confusion.
💡 Suggested wording tweak
- if token != "" {
- logger.Fatal("The --token flag is no longer supported. Please update your Agentuity CLI to the latest version.")
- }
+ if token != "" {
+ logger.Fatal("The --token flag is no longer supported. Remove --token/AGENTUITY_SDK_KEY and use --private-key instead.")
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| maybePrivateKey, _ := flags.GetString("private-key") | |
| endpointID, _ := flags.GetString("endpoint-id") | |
| gravityUrl, _ := flags.GetString("url") | |
| healthCheck, _ := flags.GetBool("health-check") | |
| token, _ := flags.GetString("token") | |
| if token != "" { | |
| logger.Fatal("The --token flag is no longer supported. Please update your Agentuity CLI to the latest version.") | |
| } | |
| maybePrivateKey, _ := flags.GetString("private-key") | |
| endpointID, _ := flags.GetString("endpoint-id") | |
| gravityUrl, _ := flags.GetString("url") | |
| healthCheck, _ := flags.GetBool("health-check") | |
| token, _ := flags.GetString("token") | |
| if token != "" { | |
| logger.Fatal("The --token flag is no longer supported. Remove --token/AGENTUITY_SDK_KEY and use --private-key instead.") | |
| } |
🤖 Prompt for AI Agents
In `@cmd/start.go` around lines 67 - 75, Change the unclear deprecation log for
the token flag: when reading token via flags.GetString("token") and detecting
token != "", update the logger.Fatal message (the code around maybePrivateKey
and flags) to explicitly instruct users to stop using --token/AGENTUITY_SDK_KEY
and to use --private-key (or the private key env/flag) instead — reference the
token variable and the logger.Fatal call and replace the generic "update your
Agentuity CLI" text with a clear, actionable message that says remove
--token/AGENTUITY_SDK_KEY and supply the private key via --private-key.
| privateKeyPEM, err := loadPrivateKeyPEM(maybePrivateKey) | ||
| if err != nil { | ||
| logger.Fatal("failed to load private key: %v", err) | ||
| } | ||
|
|
||
| block, _ := pem.Decode(privateKeyPEM) | ||
| if block == nil { | ||
| logger.Fatal("no PEM block found in --private-key") | ||
| } | ||
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | ||
| if err != nil { | ||
| // Try SEC 1/EC format as fallback | ||
| key, err = x509.ParseECPrivateKey(block.Bytes) | ||
| if err != nil { | ||
| logger.Fatal("failed to parse private key: %v (pem length: %d)", err, len(privateKeyPEM)) | ||
| } | ||
| } | ||
| privateKey, ok := key.(*ecdsa.PrivateKey) | ||
| if !ok { | ||
| logger.Fatal("private key is not an ECDSA key") | ||
| } | ||
|
|
||
| agent := stack.AgentMetadata{ | ||
| OrgID: orgID, | ||
| ProjectID: projectID, | ||
| InstanceID: endpointID, | ||
| PrivateKey: privateKey, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's find and read the file cmd/start.go
find . -name "cmd" -type d | head -5
find . -name "start.go" -type f | head -5Repository: agentuity/gravity
Length of output: 81
🏁 Script executed:
#!/bin/bash
# Read the specific lines in question (82-109)
if [ -f "cmd/start.go" ]; then
echo "=== cmd/start.go lines 75-115 ==="
sed -n '75,115p' "cmd/start.go" | cat -n
else
echo "File not found, searching..."
find . -name "start.go" -type f -exec wc -l {} \; -exec echo "File: {}" \;
fiRepository: agentuity/gravity
Length of output: 1535
🏁 Script executed:
#!/bin/bash
# Also check if there are any tests or examples showing how privateKeyPEM is used
rg -A 5 -B 5 "pem.Decode" --type goRepository: agentuity/gravity
Length of output: 1754
🏁 Script executed:
#!/bin/bash
# Find the loadPrivateKeyPEM function
rg -A 15 "func loadPrivateKeyPEM" cmd/start.goRepository: agentuity/gravity
Length of output: 524
🏁 Script executed:
#!/bin/bash
# Check for tests or examples that show input format
find . -name "*test*.go" -type f -exec rg -l "privateKeyPEM\|loadPrivateKeyPEM" {} \;Repository: agentuity/gravity
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Check Go's crypto/x509 documentation via looking at how EC PARAMETERS blocks work
# EC PARAMETERS are sometimes generated in openssl EC key output
# Let's see if there are any examples in the codebase
rg -i "EC PARAMETERS|ec parameters" .Repository: agentuity/gravity
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Get the full loadPrivateKeyPEM function
rg -A 30 "func loadPrivateKeyPEM" cmd/start.goRepository: agentuity/gravity
Length of output: 1116
🏁 Script executed:
#!/bin/bash
# Look at the parsePEMBundle implementation in stack.go to see the pattern
rg -A 25 "func parsePEMBundle" internal/stack/stack.goRepository: agentuity/gravity
Length of output: 665
🏁 Script executed:
#!/bin/bash
# Check Go's crypto documentation online to confirm pem.Decode behavior
# Let's also look for any comments about key format expectations
rg -B 5 -A 5 "PKCS8\|ParseEC" cmd/start.goRepository: agentuity/gravity
Length of output: 43
Loop through PEM blocks to find a private key block instead of parsing the first block unconditionally.
PEM files may contain EC PARAMETERS blocks, certificates, or other data before the private key. The current code calls pem.Decode() once and attempts to parse whatever block it returns, which fails if that block isn't a key. Search for a block matching "PRIVATE KEY" to ensure valid keys aren't rejected.
This pattern is already established in parsePEMBundle() in internal/stack/stack.go, which iterates through blocks and checks block.Type.
Suggested fix
- block, _ := pem.Decode(privateKeyPEM)
- if block == nil {
- logger.Fatal("no PEM block found in --private-key")
- }
+ var block *pem.Block
+ rest := privateKeyPEM
+ for {
+ var b *pem.Block
+ b, rest = pem.Decode(rest)
+ if b == nil {
+ break
+ }
+ if strings.Contains(b.Type, "PRIVATE KEY") {
+ block = b
+ break
+ }
+ }
+ if block == nil {
+ logger.Fatal("no private key PEM block found in --private-key")
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| privateKeyPEM, err := loadPrivateKeyPEM(maybePrivateKey) | |
| if err != nil { | |
| logger.Fatal("failed to load private key: %v", err) | |
| } | |
| block, _ := pem.Decode(privateKeyPEM) | |
| if block == nil { | |
| logger.Fatal("no PEM block found in --private-key") | |
| } | |
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | |
| if err != nil { | |
| // Try SEC 1/EC format as fallback | |
| key, err = x509.ParseECPrivateKey(block.Bytes) | |
| if err != nil { | |
| logger.Fatal("failed to parse private key: %v (pem length: %d)", err, len(privateKeyPEM)) | |
| } | |
| } | |
| privateKey, ok := key.(*ecdsa.PrivateKey) | |
| if !ok { | |
| logger.Fatal("private key is not an ECDSA key") | |
| } | |
| agent := stack.AgentMetadata{ | |
| OrgID: orgID, | |
| ProjectID: projectID, | |
| InstanceID: endpointID, | |
| PrivateKey: privateKey, | |
| } | |
| privateKeyPEM, err := loadPrivateKeyPEM(maybePrivateKey) | |
| if err != nil { | |
| logger.Fatal("failed to load private key: %v", err) | |
| } | |
| var block *pem.Block | |
| rest := privateKeyPEM | |
| for { | |
| var b *pem.Block | |
| b, rest = pem.Decode(rest) | |
| if b == nil { | |
| break | |
| } | |
| if strings.Contains(b.Type, "PRIVATE KEY") { | |
| block = b | |
| break | |
| } | |
| } | |
| if block == nil { | |
| logger.Fatal("no private key PEM block found in --private-key") | |
| } | |
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | |
| if err != nil { | |
| // Try SEC 1/EC format as fallback | |
| key, err = x509.ParseECPrivateKey(block.Bytes) | |
| if err != nil { | |
| logger.Fatal("failed to parse private key: %v (pem length: %d)", err, len(privateKeyPEM)) | |
| } | |
| } | |
| privateKey, ok := key.(*ecdsa.PrivateKey) | |
| if !ok { | |
| logger.Fatal("private key is not an ECDSA key") | |
| } | |
| agent := stack.AgentMetadata{ | |
| OrgID: orgID, | |
| ProjectID: projectID, | |
| InstanceID: endpointID, | |
| PrivateKey: privateKey, | |
| } |
🤖 Prompt for AI Agents
In `@cmd/start.go` around lines 82 - 109, The current logic decodes only the first
PEM block from privateKeyPEM and fails if that block isn't a key; update the
parsing near pem.Decode(privateKeyPEM) to iterate with pem.Decode in a loop over
the remaining bytes and select a block whose block.Type contains "PRIVATE KEY"
(or ends with "PRIVATE KEY") before attempting x509.ParsePKCS8PrivateKey /
x509.ParseECPrivateKey; for each candidate block try PKCS#8 then EC parsing (as
the existing fallback does) and only break/assign privateKey when parsing
succeeds, otherwise continue to the next PEM block; if no private-key block
parses successfully, call logger.Fatal with an informative message (include
len(privateKeyPEM) as currently done).
| var server *http.Server | ||
| var serverMu sync.Mutex | ||
| provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error { | ||
| serverMu.Lock() | ||
| oldServer := server | ||
| server = nil | ||
| serverMu.Unlock() | ||
| if oldServer != nil { | ||
| if err := oldServer.Shutdown(ctx); err != nil { | ||
| logger.Error("failed to shutdown server: %v", err) | ||
| } | ||
| } | ||
| tlsConfig, err := stack.GenerateCertificate(ctx, logger, c.MachineCertBundle) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to generate certificate: %w", err) | ||
| } | ||
| newServer, err := stack.StartServer(ctx, logger, tlsConfig, urls) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to start server: %w", err) | ||
| } | ||
| serverMu.Lock() | ||
| server = newServer | ||
| serverMu.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Script executed:
git ls-files cmd/ | head -20Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
cmd/start.go
Script executed:
cat -n cmd/start.go | sed -n '120,170p'Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
120 LocalPort: localPort,
121 ProxyPort: proxyPort,
122 URL: gravityUrl,
123 Version: version,
124 }
125
126 netStack, linkEP, err := stack.CreateNetworkStack(logger, urls)
127 if err != nil {
128 logger.Fatal("failed to create network stack: %v", err)
129 }
130 defer netStack.Close()
131 defer linkEP.Close()
132
133 var server *http.Server
134 var serverMu sync.Mutex
135 provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error {
136 serverMu.Lock()
137 oldServer := server
138 server = nil
139 serverMu.Unlock()
140 if oldServer != nil {
141 if err := oldServer.Shutdown(ctx); err != nil {
142 logger.Error("failed to shutdown server: %v", err)
143 }
144 }
145 tlsConfig, err := stack.GenerateCertificate(ctx, logger, c.MachineCertBundle)
146 if err != nil {
147 return fmt.Errorf("failed to generate certificate: %w", err)
148 }
149 newServer, err := stack.StartServer(ctx, logger, tlsConfig, urls)
150 if err != nil {
151 return fmt.Errorf("failed to start server: %w", err)
152 }
153 serverMu.Lock()
154 server = newServer
155 serverMu.Unlock()
156 return nil
157 })
158 if err != nil {
159 logger.Fatal("failed to create network provider: %v", err)
160 }
161
162 // Wait for provider connection
163 select {
164 case <-ctx.Done():
165 logger.Fatal("context done: %v", ctx.Err())
166 case <-time.After(time.Second * 10):
167 logger.Error("timed out waiting for provider connection")
168 os.Exit(1)
169 case <-provider.Connected:
170 logger.Debug("✅ Connected to Gravity! Proxy is ready.")
Script executed:
cat -n cmd/start.go | sed -n '1,140p' | grep -E "(func |ctx\s*:=|ctx\s*=)"Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 1
Script executed:
cat -n cmd/start.go | head -80Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
1 package cmd
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/x509"
7 "encoding/base64"
8 "encoding/pem"
9 "fmt"
10 "net/http"
11 "os"
12 "os/signal"
13 "runtime/debug"
14 "strings"
15 "sync"
16 "syscall"
17 "time"
18
19 "github.com/agentuity/go-common/gravity/provider"
20 _logger "github.com/agentuity/go-common/logger"
21 cnet "github.com/agentuity/go-common/network"
22 csys "github.com/agentuity/go-common/sys"
23 "github.com/agentuity/gravity/internal/heartbeat"
24 "github.com/agentuity/gravity/internal/stack"
25 "github.com/agentuity/gravity/internal/utils"
26 "github.com/spf13/cobra"
27 )
28
29 var version string = "dev"
30
31 var rootCmd = &cobra.Command{
32 Use: "gravity",
33 Short: "Run the gravity client",
34 Args: cobra.NoArgs,
35 Run: func(cmd *cobra.Command, args []string) {
36 flags := cmd.Flags()
37
38 var logLevel _logger.LogLevel
39 logLevelStr, _ := flags.GetString("log-level")
40 if logLevelStr == "" {
41 logLevel = _logger.GetLevelFromEnv()
42 } else {
43 switch logLevelStr {
44 case "info", "INFO":
45 logLevel = _logger.LevelInfo
46 case "debug", "DEBUG":
47 logLevel = _logger.LevelDebug
48 case "warn", "WARN":
49 logLevel = _logger.LevelWarn
50 case "trace", "TRACE":
51 logLevel = _logger.LevelTrace
52 case "error", "ERROR":
53 logLevel = _logger.LevelError
54 default:
55 logLevel = _logger.LevelTrace
56 }
57 }
58
59 logger := _logger.NewConsoleLogger(logLevel)
60
61 ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
62 defer cancel()
63
64 localPort, _ := flags.GetInt("port")
65 orgID, _ := flags.GetString("org-id")
66 projectID, _ := flags.GetString("project-id")
67 maybePrivateKey, _ := flags.GetString("private-key")
68 endpointID, _ := flags.GetString("endpoint-id")
69 gravityUrl, _ := flags.GetString("url")
70 healthCheck, _ := flags.GetBool("health-check")
71 token, _ := flags.GetString("token")
72
73 if token != "" {
74 logger.Fatal("The --token flag is no longer supported. Please update your Agentuity CLI to the latest version.")
75 }
76
77 ipv4addr, err := utils.GetPrivateIPv4()
78 if err != nil {
79 logger.Fatal("failed to get private IPv4: %v", err)
80 }
Script executed:
fd -t f "\.go$" internal/stack/ | head -20Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
internal/stack/certs.go
internal/stack/gravityClient.go
internal/stack/networkProvider.go
internal/stack/stack.go
Script executed:
rg "func CreateNetworkProvider" --type go -A 20Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
internal/stack/stack.go:func CreateNetworkProvider(
internal/stack/stack.go- ctx context.Context,
internal/stack/stack.go- logger _logger.Logger,
internal/stack/stack.go- linkEP *channel.Endpoint,
internal/stack/stack.go- urls UrlsMetadata,
internal/stack/stack.go- agent AgentMetadata,
internal/stack/stack.go- onConnect func(*provider.Configuration) error,
internal/stack/stack.go-) (*GravityClient, *gravity.GravityClient, error) {
internal/stack/stack.go-
internal/stack/stack.go- // Egress pump to send outbound packets to Gravity
internal/stack/stack.go- var network networkProvider
internal/stack/stack.go-
internal/stack/stack.go- go func() {
internal/stack/stack.go- for {
internal/stack/stack.go- select {
internal/stack/stack.go- case <-ctx.Done():
internal/stack/stack.go- return
internal/stack/stack.go- default:
internal/stack/stack.go- pkt := linkEP.ReadContext(ctx)
internal/stack/stack.go- if pkt == nil {
internal/stack/stack.go- continue
Script executed:
rg "onConnect" internal/stack/stack.go -B 3 -A 3Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
linkEP *channel.Endpoint,
urls UrlsMetadata,
agent AgentMetadata,
onConnect func(*provider.Configuration) error,
) (*GravityClient, *gravity.GravityClient, error) {
// Egress pump to send outbound packets to Gravity
--
}()
prov := GravityClient{
onConnect: onConnect,
logger: logger,
ep: linkEP,
Connected: make(chan struct{}, 1),
Script executed:
cat -n internal/stack/stack.go | wc -lRepository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
455
Script executed:
cat -n internal/stack/stack.go | sed -n '50,150p'Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
50 Token string
51 URL string
52 Version string
53 }
54
55 func ProvisionGravity(ctx context.Context, logger _logger.Logger, agent AgentMetadata, urls UrlsMetadata) (*proto.ProvisionResponse, error) {
56
57 hostname, err := os.Hostname()
58 if err != nil {
59 return nil, fmt.Errorf("error getting hostname: %w", err)
60 }
61
62 requestObj := gravity.ProvisionRequest{
63 Context: ctx,
64 GravityURL: urls.URL,
65 InstanceID: agent.InstanceID,
66 Region: "unknown",
67 Provider: "other",
68 PrivateIP: urls.IPv4Addr,
69 Token: urls.Token,
70 Hostname: hostname,
71 Ephemeral: true,
72 Capabilities: &proto.ClientCapabilities{
73 DynamicHostname: true,
74 // DynamicProjectRouting: "",
75 },
76 }
77
78 logger.Debug("Provisioning gravity connection...")
79 resp, err := gravity.Provision(requestObj)
80 if err != nil {
81 return nil, fmt.Errorf("failed to provision: %w", err)
82 }
83
84 return resp, nil
85 }
86
87 func GenerateCertificate(_ context.Context, logger _logger.Logger, bundle string) (*tls.Config, error) {
88 certPEM, caCertPEM, keyPEM, err := parsePEMBundle(bundle)
89 if err != nil {
90 return nil, fmt.Errorf("failed to parse certificate bundle: %w", err)
91 }
92
93 // Set up TLS config
94 cert, err := tls.X509KeyPair(certPEM, keyPEM)
95 if err != nil {
96 return nil, fmt.Errorf("failed to load certificate: %w", err)
97 }
98
99 // Parse the certificate to log details
100 if len(cert.Certificate) > 0 {
101 x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
102 if err == nil {
103 cert.Leaf = x509Cert
104 logger.Debug("Loaded certificate: CN=%s, SANs=%v, NotBefore=%v, NotAfter=%v",
105 x509Cert.Subject.CommonName, x509Cert.DNSNames, x509Cert.NotBefore, x509Cert.NotAfter)
106 } else {
107 logger.Warn("Failed to parse certificate for logging: %v", err)
108 }
109 }
110
111 caCertPool := x509.NewCertPool()
112 if !caCertPool.AppendCertsFromPEM(caCertPEM) {
113 return nil, fmt.Errorf("failed to parse CA certificate")
114 }
115
116 // Parse and log CA certificate details
117 caCert, err := x509.ParseCertificate(caCertPEM)
118 if err == nil {
119 logger.Debug("Loaded CA certificate: CN=%s, Issuer=%s", caCert.Subject.CommonName, caCert.Issuer.CommonName)
120 } else {
121 // Try parsing as PEM
122 block, _ := pem.Decode(caCertPEM)
123 if block != nil {
124 caCert, err = x509.ParseCertificate(block.Bytes)
125 if err == nil {
126 logger.Debug("Loaded CA certificate (PEM): CN=%s, Issuer=%s", caCert.Subject.CommonName, caCert.Issuer.CommonName)
127 }
128 }
129 }
130
131 tlsConfig := &tls.Config{
132 Certificates: []tls.Certificate{cert},
133 RootCAs: caCertPool,
134 ClientCAs: caCertPool, // Also set ClientCAs for mutual TLS
135 ClientAuth: tls.VerifyClientCertIfGiven,
136 MinVersion: tls.VersionTLS13,
137 CurvePreferences: []tls.CurveID{tls.X25519, tls.X25519MLKEM768, tls.CurveP256},
138 NextProtos: []string{"h2", "http/1.1"},
139 GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
140 remoteAddr := "<no-remote>"
141 if hello != nil && hello.Conn != nil {
142 remoteAddr = hello.Conn.RemoteAddr().String()
143 }
144 logger.Trace("TLS GetCertificate: ServerName=%s, SupportedProtos=%v, RemoteAddr=%v",
145 hello.ServerName, hello.SupportedProtos, remoteAddr)
146 return &cert, nil
147 },
148 VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
149 logger.Trace("TLS VerifyPeerCertificate: chains=%d, rawCerts=%d", len(verifiedChains), len(rawCerts))
150 if len(rawCerts) > 0 {
Script executed:
rg "\.onConnect\(" internal/stack/ -B 5 -A 5Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
internal/stack/gravityClient.go-
internal/stack/gravityClient.go-func (p *GravityClient) Configure(config provider.Configuration) error {
internal/stack/gravityClient.go- p.logger.Debug("configuring provider")
internal/stack/gravityClient.go- p.config = config
internal/stack/gravityClient.go- p.Connected <- struct{}{}
internal/stack/gravityClient.go: return p.onConnect(&config)
internal/stack/gravityClient.go-}
internal/stack/gravityClient.go-
internal/stack/gravityClient.go-func (p *GravityClient) Deprovision(ctx context.Context, resourceID string, reason provider.DeprovisionReason) error {
internal/stack/gravityClient.go- return nil
internal/stack/gravityClient.go-}
Script executed:
cat -n internal/stack/gravityClient.goRepository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
1 package stack
2
3 import (
4 "context"
5
6 "github.com/agentuity/go-common/gravity/proto"
7 "github.com/agentuity/go-common/gravity/provider"
8 "github.com/agentuity/go-common/logger"
9 "gvisor.dev/gvisor/pkg/buffer"
10 "gvisor.dev/gvisor/pkg/tcpip/link/channel"
11 "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
12 "gvisor.dev/gvisor/pkg/tcpip/stack"
13 )
14
15 type GravityClient struct {
16 ep *channel.Endpoint
17 logger logger.Logger
18 config provider.Configuration
19 Connected chan struct{}
20 onConnect func(*provider.Configuration) error
21 }
22
23 func (p *GravityClient) Configure(config provider.Configuration) error {
24 p.logger.Debug("configuring provider")
25 p.config = config
26 p.Connected <- struct{}{}
27 return p.onConnect(&config)
28 }
29
30 func (p *GravityClient) Deprovision(ctx context.Context, resourceID string, reason provider.DeprovisionReason) error {
31 return nil
32 }
33
34 func (p *GravityClient) Resources() []*proto.ExistingDeployment {
35 return nil
36 }
37
38 func (p *GravityClient) SetMetricsCollector(collector provider.ProjectRuntimeStatsCollector) {
39 }
40
41 func (p *GravityClient) ProcessInPacket(payload []byte) {
42 if p.ep == nil {
43 p.logger.Error("ProcessInPacket called but endpoint is nil")
44 return
45 }
46 pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{})
47 view := buffer.NewView(len(payload))
48 view.Write(payload)
49 pkt.Data().AppendView(view)
50 p.ep.InjectInbound(ipv6.ProtocolNumber, pkt)
51 // Note: Don't call DecRef here - the netstack will handle the packet lifecycle
52 }
Script executed:
rg "\.Configure\(" --type go | head -20Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
Script executed:
rg "provider\.Configuration" --type go -B 3 -A 3 | head -50Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
internal/stack/gravityClient.go-type GravityClient struct {
internal/stack/gravityClient.go- ep *channel.Endpoint
internal/stack/gravityClient.go- logger logger.Logger
internal/stack/gravityClient.go: config provider.Configuration
internal/stack/gravityClient.go- Connected chan struct{}
internal/stack/gravityClient.go: onConnect func(*provider.Configuration) error
internal/stack/gravityClient.go-}
internal/stack/gravityClient.go-
internal/stack/gravityClient.go:func (p *GravityClient) Configure(config provider.Configuration) error {
internal/stack/gravityClient.go- p.logger.Debug("configuring provider")
internal/stack/gravityClient.go- p.config = config
internal/stack/gravityClient.go- p.Connected <- struct{}{}
--
internal/stack/stack.go- linkEP *channel.Endpoint,
internal/stack/stack.go- urls UrlsMetadata,
internal/stack/stack.go- agent AgentMetadata,
internal/stack/stack.go: onConnect func(*provider.Configuration) error,
internal/stack/stack.go-) (*GravityClient, *gravity.GravityClient, error) {
internal/stack/stack.go-
internal/stack/stack.go- // Egress pump to send outbound packets to Gravity
--
cmd/start.go-
cmd/start.go- var server *http.Server
cmd/start.go- var serverMu sync.Mutex
cmd/start.go: provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error {
cmd/start.go- serverMu.Lock()
cmd/start.go- oldServer := server
cmd/start.go- server = nil
Script executed:
rg "func.*Shutdown" --type go -B 2 -A 5Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
internal/heartbeat/heartbeat.go-}
internal/heartbeat/heartbeat.go-
internal/heartbeat/heartbeat.go:func (s *Server) Shutdown() {
internal/heartbeat/heartbeat.go- s.shutdownOnce.Do(func() {
internal/heartbeat/heartbeat.go- if s.cancel != nil {
internal/heartbeat/heartbeat.go- s.cancel()
internal/heartbeat/heartbeat.go- }
internal/heartbeat/heartbeat.go- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
Script executed:
cat -n cmd/start.go | sed -n '135,160p'Repository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 0
stdout:
135 provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error {
136 serverMu.Lock()
137 oldServer := server
138 server = nil
139 serverMu.Unlock()
140 if oldServer != nil {
141 if err := oldServer.Shutdown(ctx); err != nil {
142 logger.Error("failed to shutdown server: %v", err)
143 }
144 }
145 tlsConfig, err := stack.GenerateCertificate(ctx, logger, c.MachineCertBundle)
146 if err != nil {
147 return fmt.Errorf("failed to generate certificate: %w", err)
148 }
149 newServer, err := stack.StartServer(ctx, logger, tlsConfig, urls)
150 if err != nil {
151 return fmt.Errorf("failed to start server: %w", err)
152 }
153 serverMu.Lock()
154 server = newServer
155 serverMu.Unlock()
156 return nil
157 })
158 if err != nil {
159 logger.Fatal("failed to create network provider: %v", err)
160 }
Script executed:
web_search
http.Server.Shutdown behavior with context timeoutRepository: agentuity/gravity
Repository: agentuity/gravity
Exit code: 127
stderr:
/bin/bash: line 1: web_search: command not found
/bin/bash: line 2: http.Server.Shutdown: command not found
http.Server.Shutdown context timeout behavior golang
In Go’s net/http, (*http.Server).Shutdown(ctx) is graceful and the ctx timeout only bounds how long Shutdown will wait, not how long active requests are allowed to run.
What Shutdown does
Shutdown performs this sequence: (1) close listeners (stop accepting new connections), (2) close idle connections, (3) wait for remaining connections to become idle so they can be closed. If ctx expires first, Shutdown returns ctx.Err() (typically context.DeadlineExceeded). It also does not close/wait for hijacked connections (e.g., WebSockets). [1]
What happens when the context times out
From the actual implementation, when the context is done, Shutdown just returns ctx.Err(); it does not forcibly close active connections for you. Active handlers/connections can continue running until they finish or until something else closes them. [2]
Practical implications
- Your in-flight request
r.Context()is not automatically canceled just becauseShutdownwas called;Shutdownis explicitly “without interrupting any active connections.” [1][3] - If you need a hard cutoff after the graceful period, a common pattern is: call
Shutdown(timeoutCtx), and if it returnscontext.DeadlineExceeded, then callsrv.Close()to force-close remaining connections. (Closecloses connections inStateNew,StateActive, andStateIdle.) [1]
Sources:
[1] Go net/http docs for Server.Shutdown / Server.Close (pkg.go.dev) (pkg.go.dev)
[2] Go net/http Server.Shutdown implementation showing case <-ctx.Done(): return ctx.Err() (go.googlesource.com) (go.googlesource.com)
[3] Discussion confirming Shutdown doesn’t interrupt active connections / doesn’t cancel request contexts (Stack Overflow) (stackoverflow.com)
Citations:
- 1: https://pkg.go.dev/net/http%40go1.25.4?utm_source=openai
- 2: https://go.googlesource.com/go/%2B/go1.9.3/src/net/http/server.go
- 3: https://stackoverflow.com/questions/49851014/can-i-use-context-done-to-check-server-shutdown?utm_source=openai
Use a bounded shutdown context to prevent blocking reconfiguration.
During provider reconfiguration, oldServer.Shutdown(ctx) uses the long-lived root context which can block indefinitely if a client connection is stuck. This blocks the entire onConnect callback, preventing the new server from starting and effectively halting provider reconfiguration.
Use a timeout context for shutdown to ensure the callback completes within a reasonable grace period:
⏱️ Use a bounded shutdown context
if oldServer != nil {
+ shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
+ defer cancel()
- if err := oldServer.Shutdown(ctx); err != nil {
+ if err := oldServer.Shutdown(shutdownCtx); err != nil {
logger.Error("failed to shutdown server: %v", err)
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var server *http.Server | |
| var serverMu sync.Mutex | |
| provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error { | |
| serverMu.Lock() | |
| oldServer := server | |
| server = nil | |
| serverMu.Unlock() | |
| if oldServer != nil { | |
| if err := oldServer.Shutdown(ctx); err != nil { | |
| logger.Error("failed to shutdown server: %v", err) | |
| } | |
| } | |
| tlsConfig, err := stack.GenerateCertificate(ctx, logger, c.MachineCertBundle) | |
| if err != nil { | |
| return fmt.Errorf("failed to generate certificate: %w", err) | |
| } | |
| newServer, err := stack.StartServer(ctx, logger, tlsConfig, urls) | |
| if err != nil { | |
| return fmt.Errorf("failed to start server: %w", err) | |
| } | |
| serverMu.Lock() | |
| server = newServer | |
| serverMu.Unlock() | |
| var server *http.Server | |
| var serverMu sync.Mutex | |
| provider, client, err := stack.CreateNetworkProvider(ctx, logger, linkEP, urls, agent, func(c *provider.Configuration) error { | |
| serverMu.Lock() | |
| oldServer := server | |
| server = nil | |
| serverMu.Unlock() | |
| if oldServer != nil { | |
| shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| defer cancel() | |
| if err := oldServer.Shutdown(shutdownCtx); err != nil { | |
| logger.Error("failed to shutdown server: %v", err) | |
| } | |
| } | |
| tlsConfig, err := stack.GenerateCertificate(ctx, logger, c.MachineCertBundle) | |
| if err != nil { | |
| return fmt.Errorf("failed to generate certificate: %w", err) | |
| } | |
| newServer, err := stack.StartServer(ctx, logger, tlsConfig, urls) | |
| if err != nil { | |
| return fmt.Errorf("failed to start server: %w", err) | |
| } | |
| serverMu.Lock() | |
| server = newServer | |
| serverMu.Unlock() |
🤖 Prompt for AI Agents
In `@cmd/start.go` around lines 133 - 155, The shutdown of oldServer in the
provider reconfigure callback currently calls oldServer.Shutdown(ctx) with the
long-lived root context and can block indefinitely; change that call to use a
bounded timeout context (e.g., via context.WithTimeout(parentCtx, timeout)) so
the shutdown completes or aborts within a fixed grace period, ensure you call
cancel() and handle/log Shutdown errors including context deadline exceeded, and
keep the rest of the callback (GenerateCertificate, StartServer, and assignment
to server under serverMu) unchanged; update the closure where oldServer is set
and Shutdown is invoked (the variables server, serverMu, oldServer in the
CreateNetworkProvider callback).
Summary
GenerateCertificateto reduce duplicate parsing workIsCAfield (order-independent)PRIVATE KEY,RSA PRIVATE KEY,EC PRIVATE KEY)--private-keyflag with support for:Changes
internal/stack/stack.go:GenerateCertificatenow accepts bundle string and parses internallycmd/start.go: Added--private-keyflag andloadPrivateKeyPEMhelperSummary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.