mirror of
https://github.com/Adembc/lazyssh.git
synced 2026-07-14 12:13:34 +02:00
127 lines
3.9 KiB
Go
127 lines
3.9 KiB
Go
// Copyright 2025.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ssh_config_file
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/kevinburke/ssh_config"
|
|
)
|
|
|
|
// loadConfig reads and parses the SSH config file.
|
|
// If the file does not exist, it returns an empty config without error to support first-run behavior.
|
|
func (r *Repository) loadConfig() (*ssh_config.Config, error) {
|
|
file, err := r.fileSystem.Open(r.configPath)
|
|
if err != nil {
|
|
if r.fileSystem.IsNotExist(err) {
|
|
return &ssh_config.Config{Hosts: []*ssh_config.Host{}}, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to open config file: %w", err)
|
|
}
|
|
defer func() {
|
|
if cerr := file.Close(); cerr != nil {
|
|
r.logger.Warnf("failed to close config file: %v", cerr)
|
|
}
|
|
}()
|
|
|
|
cfg, err := ssh_config.Decode(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode config: %w", err)
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// saveConfig writes the SSH config back to the file with atomic operations and backup management.
|
|
func (r *Repository) saveConfig(cfg *ssh_config.Config) error {
|
|
configDir := filepath.Dir(r.configPath)
|
|
|
|
tempFile, err := r.createTempFile(configDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temporary file: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if removeErr := r.fileSystem.Remove(tempFile); removeErr != nil {
|
|
r.logger.Warnf("failed to remove temporary file %s: %v", tempFile, removeErr)
|
|
}
|
|
}()
|
|
|
|
if err := r.writeConfigToFile(tempFile, cfg); err != nil {
|
|
return fmt.Errorf("failed to write config to temporary file: %w", err)
|
|
}
|
|
|
|
// Ensure a one-time original backup exists before any modifications managed by lazyssh.
|
|
if err := r.createOriginalBackupIfNeeded(); err != nil {
|
|
return fmt.Errorf("failed to create original backup: %w", err)
|
|
}
|
|
|
|
if err := r.createBackup(); err != nil {
|
|
return fmt.Errorf("failed to create backup: %w", err)
|
|
}
|
|
|
|
if err := r.fileSystem.Rename(tempFile, r.configPath); err != nil {
|
|
return fmt.Errorf("failed to atomically replace config file: %w", err)
|
|
}
|
|
|
|
r.logger.Infof("SSH config successfully updated: %s", r.configPath)
|
|
return nil
|
|
}
|
|
|
|
// writeConfigToFile writes the SSH config content to the specified file
|
|
func (r *Repository) writeConfigToFile(filePath string, cfg *ssh_config.Config) error {
|
|
file, err := r.fileSystem.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, SSHConfigPerms)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file for writing: %w", err)
|
|
}
|
|
defer func() {
|
|
if cerr := file.Close(); cerr != nil {
|
|
r.logger.Warnf("failed to close file %s: %v", filePath, cerr)
|
|
}
|
|
}()
|
|
|
|
configContent := cfg.String()
|
|
if _, err := file.WriteString(configContent); err != nil {
|
|
return fmt.Errorf("failed to write config content: %w", err)
|
|
}
|
|
|
|
if err := file.Sync(); err != nil {
|
|
return fmt.Errorf("failed to sync file to disk: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createTempFile creates a temporary file in the specified directory
|
|
func (r *Repository) createTempFile(dir string) (string, error) {
|
|
timestamp := time.Now().Format("20060102150405")
|
|
tempFileName := fmt.Sprintf("config%s%s", timestamp, TempSuffix)
|
|
tempFilePath := filepath.Join(dir, tempFileName)
|
|
|
|
// Create the temp file with explicit 0600 permissions
|
|
f, err := r.fileSystem.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, SSHConfigPerms)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if cerr := f.Close(); cerr != nil {
|
|
r.logger.Warnf("failed to close temporary file %s: %v", tempFilePath, cerr)
|
|
}
|
|
|
|
return tempFilePath, nil
|
|
}
|