mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
feat: add WireGuard VPS setup automation script (#2810)
* feat: add WireGuard VPS setup automation script Adds a comprehensive bash script that automates: - SSH key setup and authentication - WireGuard installation on remote VPS - Configuration download and import to NetworkManager - User-friendly CLI interface with validation - Detailed status messages and error handling - Instructions for exposing services via ACME/Let's Encrypt * use cat heredoc for issue files to fix formatting Replaces echo with cat heredoc when writing to /etc/issue and /etc/issue.net to properly preserve escape sequences and prevent unwanted newlines in login prompts. * add convent `wg-vps-setup` symlink to PATH * sync ssh privkey on init * Update default ssh key location * simplify to use existing StartOS SSH keys and fix .ssh permission * finetune * Switch to start9labs repo * rename some files * set correct ownership --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -25,6 +25,7 @@ use crate::context::{CliContext, InitContext};
|
||||
use crate::db::model::public::ServerStatus;
|
||||
use crate::db::model::Database;
|
||||
use crate::disk::mount::util::unmount;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::net::net_controller::{NetController, NetService};
|
||||
use crate::net::utils::find_wifi_iface;
|
||||
@@ -35,7 +36,7 @@ use crate::progress::{
|
||||
};
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||
use crate::ssh::SSH_AUTHORIZED_KEYS_FILE;
|
||||
use crate::ssh::SSH_DIR;
|
||||
use crate::system::get_mem_info;
|
||||
use crate::util::io::{create_file, IOHook};
|
||||
use crate::util::lshw::lshw;
|
||||
@@ -340,8 +341,10 @@ pub async fn init(
|
||||
|
||||
load_ssh_keys.start();
|
||||
crate::ssh::sync_keys(
|
||||
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
||||
&peek.as_private().as_ssh_privkey().de()?,
|
||||
&peek.as_private().as_ssh_pubkeys().de()?,
|
||||
SSH_AUTHORIZED_KEYS_FILE,
|
||||
SSH_DIR,
|
||||
)
|
||||
.await?;
|
||||
load_ssh_keys.complete();
|
||||
|
||||
@@ -3,20 +3,23 @@ use std::path::Path;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::eyre;
|
||||
use imbl_value::InternedString;
|
||||
use models::FromStrParser;
|
||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::hostname::Hostname;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::create_file;
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||
use crate::util::serde::{display_serializable, HandlerExtSerde, Pem, WithIoFormat};
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub const SSH_AUTHORIZED_KEYS_FILE: &str = "/home/start9/.ssh/authorized_keys";
|
||||
pub const SSH_DIR: &str = "/home/start9/.ssh";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct SshKeys(BTreeMap<InternedString, WithTimeData<SshPubKey>>);
|
||||
@@ -143,7 +146,7 @@ pub async fn add(ctx: RpcContext, AddParams { key }: AddParams) -> Result<SshKey
|
||||
))
|
||||
})
|
||||
.await?;
|
||||
sync_keys(&keys, SSH_AUTHORIZED_KEYS_FILE).await?;
|
||||
sync_pubkeys(&keys, SSH_DIR).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -175,7 +178,7 @@ pub async fn delete(
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
sync_keys(&keys, SSH_AUTHORIZED_KEYS_FILE).await
|
||||
sync_pubkeys(&keys, SSH_DIR).await
|
||||
}
|
||||
|
||||
fn display_all_ssh_keys(params: WithIoFormat<Empty>, result: Vec<SshKeyResponse>) {
|
||||
@@ -226,23 +229,90 @@ pub async fn list(ctx: RpcContext) -> Result<Vec<SshKeyResponse>, Error> {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn sync_keys<P: AsRef<Path>>(keys: &SshKeys, dest: P) -> Result<(), Error> {
|
||||
pub async fn sync_keys<P: AsRef<Path>>(
|
||||
hostname: &Hostname,
|
||||
privkey: &Pem<ssh_key::PrivateKey>,
|
||||
pubkeys: &SshKeys,
|
||||
ssh_dir: P,
|
||||
) -> Result<(), Error> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let dest = dest.as_ref();
|
||||
let ssh_dir = dest.parent().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("SSH Key File cannot be \"/\""),
|
||||
crate::ErrorKind::Filesystem,
|
||||
)
|
||||
})?;
|
||||
let ssh_dir = ssh_dir.as_ref();
|
||||
if tokio::fs::metadata(ssh_dir).await.is_err() {
|
||||
tokio::fs::create_dir_all(ssh_dir).await?;
|
||||
}
|
||||
let mut f = create_file(dest).await?;
|
||||
for key in keys.0.values() {
|
||||
|
||||
let id_alg = if privkey.0.algorithm().is_ed25519() {
|
||||
"id_ed25519"
|
||||
} else if privkey.0.algorithm().is_ecdsa() {
|
||||
"id_ecdsa"
|
||||
} else if privkey.0.algorithm().is_rsa() {
|
||||
"id_rsa"
|
||||
} else {
|
||||
"id_unknown"
|
||||
};
|
||||
|
||||
let privkey_path = ssh_dir.join(id_alg);
|
||||
let mut f = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.mode(0o600)
|
||||
.open(&privkey_path)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
ErrorKind::Filesystem,
|
||||
lazy_format!("create {privkey_path:?}"),
|
||||
)
|
||||
})?;
|
||||
f.write_all(privkey.to_string().as_bytes()).await?;
|
||||
f.write_all(b"\n").await?;
|
||||
f.sync_all().await?;
|
||||
let mut f = create_file(ssh_dir.join(id_alg).with_extension("pub")).await?;
|
||||
f.write_all(
|
||||
(privkey
|
||||
.0
|
||||
.public_key()
|
||||
.to_openssh()
|
||||
.with_kind(ErrorKind::OpenSsh)?
|
||||
+ " start9@"
|
||||
+ &*hostname.0)
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
f.write_all(b"\n").await?;
|
||||
f.sync_all().await?;
|
||||
|
||||
let mut f = create_file(ssh_dir.join("authorized_keys")).await?;
|
||||
for key in pubkeys.0.values() {
|
||||
f.write_all(key.0.to_key_format().as_bytes()).await?;
|
||||
f.write_all(b"\n").await?;
|
||||
}
|
||||
|
||||
Command::new("chown")
|
||||
.arg("-R")
|
||||
.arg("start9:startos")
|
||||
.arg(ssh_dir)
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn sync_pubkeys<P: AsRef<Path>>(pubkeys: &SshKeys, ssh_dir: P) -> Result<(), Error> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let ssh_dir = ssh_dir.as_ref();
|
||||
if tokio::fs::metadata(ssh_dir).await.is_err() {
|
||||
tokio::fs::create_dir_all(ssh_dir).await?;
|
||||
}
|
||||
|
||||
let mut f = create_file(ssh_dir.join("authorized_keys")).await?;
|
||||
for key in pubkeys.0.values() {
|
||||
f.write_all(key.0.to_key_format().as_bytes()).await?;
|
||||
f.write_all(b"\n").await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user