Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
7487e7336e
commit
d1e9eb9eb5
122
Cargo.lock
generated
122
Cargo.lock
generated
@ -17,6 +17,18 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@ -177,6 +189,15 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -192,7 +213,9 @@ dependencies = [
|
||||
"axum",
|
||||
"clap",
|
||||
"dotenv",
|
||||
"nodrift",
|
||||
"notmad",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -254,6 +277,18 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -375,6 +410,24 @@ version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@ -492,6 +545,17 @@ version = "0.2.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
@ -548,10 +612,24 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notmad"
|
||||
version = "0.5.0"
|
||||
name = "nodrift"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "508d2cebf89bcea5803868fc39ecefe647cd8aa6c6955e40987b4fb7c7604326"
|
||||
checksum = "154be26c4796e549cab55b834bb8bf6cbcd24e759ecaa6f91155464520c616ba"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notmad"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "590b2938244df27d8e6b9989dd320db6499bdad8c0a2381b4d748134998f5515"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -636,6 +714,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
@ -702,6 +786,20 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
@ -789,6 +887,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@ -1054,6 +1158,18 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -15,6 +15,8 @@ axum.workspace = true
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
||||
notmad = "0.5.0"
|
||||
notmad = "0.6.0"
|
||||
tokio-util = "0.7.12"
|
||||
async-trait = "0.1.83"
|
||||
nodrift = "0.2.0"
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
|
18
crates/churn/src/agent.rs
Normal file
18
crates/churn/src/agent.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use agent_state::AgentState;
|
||||
use refresh::AgentRefresh;
|
||||
|
||||
mod agent_state;
|
||||
|
||||
mod refresh;
|
||||
|
||||
pub async fn execute(host: impl Into<String>) -> anyhow::Result<()> {
|
||||
let state = AgentState::new().await?;
|
||||
|
||||
notmad::Mad::builder()
|
||||
.add(AgentRefresh::new(&state, host))
|
||||
.cancellation(Some(std::time::Duration::from_secs(2)))
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
32
crates/churn/src/agent/agent_state.rs
Normal file
32
crates/churn/src/agent/agent_state.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentState(Arc<State>);
|
||||
|
||||
impl AgentState {
|
||||
pub async fn new() -> anyhow::Result<Self> {
|
||||
Ok(Self(Arc::new(State::new().await?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AgentState> for AgentState {
|
||||
fn from(value: &AgentState) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AgentState {
|
||||
type Target = Arc<State>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {}
|
||||
|
||||
impl State {
|
||||
pub async fn new() -> anyhow::Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
119
crates/churn/src/agent/refresh.rs
Normal file
119
crates/churn/src/agent/refresh.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use super::agent_state::AgentState;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentRefresh {
|
||||
_state: AgentState,
|
||||
host: String,
|
||||
}
|
||||
|
||||
impl AgentRefresh {
|
||||
pub fn new(state: impl Into<AgentState>, host: impl Into<String>) -> Self {
|
||||
Self {
|
||||
_state: state.into(),
|
||||
host: host.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl notmad::Component for AgentRefresh {
|
||||
async fn run(
|
||||
&self,
|
||||
cancellation_token: tokio_util::sync::CancellationToken,
|
||||
) -> Result<(), notmad::MadError> {
|
||||
let cancel = nodrift::schedule_drifter(std::time::Duration::from_secs(5), self.clone());
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {},
|
||||
_ = cancellation_token.cancelled() => {
|
||||
tracing::debug!("cancelling agent refresh");
|
||||
cancel.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl nodrift::Drifter for AgentRefresh {
|
||||
async fn execute(&self, _token: tokio_util::sync::CancellationToken) -> anyhow::Result<()> {
|
||||
tracing::info!(host = self.host, "refreshing agent");
|
||||
|
||||
// Get plan
|
||||
let plan = Plan::new();
|
||||
let tasks = plan.tasks().await?;
|
||||
|
||||
// For task
|
||||
for task in tasks {
|
||||
// Check idempotency rules
|
||||
if !task.should_run().await? {
|
||||
tracing::debug!(task = task.id(), "skipping run");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Run task if not valid
|
||||
tracing::info!(task = task.id(), "executing task");
|
||||
task.execute().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Plan {}
|
||||
impl Plan {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub async fn tasks(&self) -> anyhow::Result<Vec<Task>> {
|
||||
Ok(vec![Task::new()])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Task {}
|
||||
|
||||
impl Task {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
"apt".into()
|
||||
}
|
||||
|
||||
pub async fn should_run(&self) -> anyhow::Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn execute(&self) -> anyhow::Result<()> {
|
||||
let mut cmd = tokio::process::Command::new("apt");
|
||||
cmd.args(["apt-get", "update", "-q"]);
|
||||
let output = cmd.output().await?;
|
||||
match output.status.success() {
|
||||
true => tracing::info!("successfully ran apt update"),
|
||||
false => {
|
||||
anyhow::bail!(
|
||||
"failed to run apt update: {}",
|
||||
std::str::from_utf8(&output.stderr)?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = tokio::process::Command::new("apt");
|
||||
cmd.env("DEBIAN_FRONTEND", "noninteractive")
|
||||
.args(["apt-get", "upgrade", "-y"]);
|
||||
let output = cmd.output().await?;
|
||||
match output.status.success() {
|
||||
true => tracing::info!("successfully ran apt upgrade"),
|
||||
false => {
|
||||
anyhow::bail!(
|
||||
"failed to run apt upgrade: {}",
|
||||
std::str::from_utf8(&output.stderr)?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -2,19 +2,28 @@ use std::net::SocketAddr;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::{api, state::SharedState};
|
||||
use crate::{agent, api, state::SharedState};
|
||||
|
||||
pub async fn execute() -> anyhow::Result<()> {
|
||||
let state = SharedState::new().await?;
|
||||
|
||||
let cli = Command::parse();
|
||||
if let Some(Commands::Serve { host }) = cli.command {
|
||||
tracing::info!("Starting service");
|
||||
match cli.command.expect("to have a subcommand") {
|
||||
Commands::Serve { host } => {
|
||||
tracing::info!("Starting service");
|
||||
|
||||
notmad::Mad::builder()
|
||||
.add(api::Api::new(&state, host))
|
||||
.run()
|
||||
.await?;
|
||||
notmad::Mad::builder()
|
||||
.add(api::Api::new(&state, host))
|
||||
.run()
|
||||
.await?;
|
||||
}
|
||||
Commands::Agent { commands } => match commands {
|
||||
AgentCommands::Start { host } => {
|
||||
tracing::info!("starting agent");
|
||||
agent::execute(host).await?;
|
||||
tracing::info!("shut down agent");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -33,4 +42,16 @@ enum Commands {
|
||||
#[arg(env = "SERVICE_HOST", long, default_value = "127.0.0.1:3000")]
|
||||
host: SocketAddr,
|
||||
},
|
||||
Agent {
|
||||
#[command(subcommand)]
|
||||
commands: AgentCommands,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum AgentCommands {
|
||||
Start {
|
||||
#[arg(env = "SERVICE_HOST", long = "service-host")]
|
||||
host: String,
|
||||
},
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ mod api;
|
||||
mod cli;
|
||||
mod state;
|
||||
|
||||
mod agent;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
|
97
install.sh
Normal file
97
install.sh
Normal file
@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration variables
|
||||
GITEA_HOST="https://git.front.kjuulh.io"
|
||||
REPO_OWNER="kjuulh"
|
||||
REPO_NAME="churn-v2"
|
||||
BINARY_NAME="churn"
|
||||
SERVICE_NAME="churn-agent"
|
||||
SERVICE_USER="churn"
|
||||
RELEASE_TAG="latest" # or specific version like "v1.0.0"
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create service user if it doesn't exist
|
||||
if ! id "$SERVICE_USER" &>/dev/null; then
|
||||
useradd -r -s /bin/false "$SERVICE_USER"
|
||||
fi
|
||||
|
||||
# Function to get latest release if RELEASE_TAG is "latest"
|
||||
get_latest_release() {
|
||||
curl -s "https://$GITEA_HOST/api/v1/repos/$REPO_OWNER/$REPO_NAME/releases/latest" | \
|
||||
grep '"tag_name":' | \
|
||||
sed -E 's/.*"([^"]+)".*/\1/'
|
||||
}
|
||||
|
||||
# Determine the actual release tag
|
||||
if [ "$RELEASE_TAG" = "latest" ]; then
|
||||
RELEASE_TAG=$(get_latest_release)
|
||||
fi
|
||||
|
||||
echo "Installing $BINARY_NAME version $RELEASE_TAG..."
|
||||
|
||||
# Download and install binary
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cd "$TMP_DIR"
|
||||
|
||||
# Download binary from Gitea
|
||||
curl -L -o "$BINARY_NAME" \
|
||||
"https://$GITEA_HOST/$REPO_OWNER/$REPO_NAME/releases/download/$RELEASE_TAG/$BINARY_NAME"
|
||||
|
||||
# Make binary executable and move to appropriate location
|
||||
chmod +x "$BINARY_NAME"
|
||||
mv "$BINARY_NAME" "/usr/local/bin/$BINARY_NAME"
|
||||
|
||||
# Create systemd service file
|
||||
cat > "/etc/systemd/system/$SERVICE_NAME.service" << EOF
|
||||
[Unit]
|
||||
Description=$SERVICE_NAME Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$SERVICE_USER
|
||||
ExecStart=/usr/local/bin/$BINARY_NAME
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
# Security hardening options
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
NoNewPrivileges=true
|
||||
ReadWritePaths=/var/log/$SERVICE_NAME
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Create log directory if logging is needed
|
||||
mkdir -p "/var/log/$SERVICE_NAME"
|
||||
chown "$SERVICE_USER:$SERVICE_USER" "/var/log/$SERVICE_NAME"
|
||||
|
||||
# Reload systemd and enable service
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
systemctl start "$SERVICE_NAME"
|
||||
|
||||
# Clean up
|
||||
cd
|
||||
rm -rf "$TMP_DIR"
|
||||
|
||||
echo "Installation complete! Service status:"
|
||||
systemctl status "$SERVICE_NAME"
|
||||
|
||||
# Provide some helpful commands
|
||||
echo "
|
||||
Useful commands:
|
||||
- Check status: systemctl status $SERVICE_NAME
|
||||
- View logs: journalctl -u $SERVICE_NAME
|
||||
- Restart service: systemctl restart $SERVICE_NAME
|
||||
- Stop service: systemctl stop $SERVICE_NAME
|
||||
"
|
Loading…
Reference in New Issue
Block a user