Compare commits

..

6 Commits

Author SHA1 Message Date
Sithies 44606e820c Merge pull request 'Automated Deployment and local ollama' (#1) from Automate-Deployment into master
CI / check (push) Has been cancelled
CI / test (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-infra (push) Has been cancelled
Reviewed-on: #1
2026-04-25 18:55:35 +00:00
Sithies 94eb9992a3 change local model
CI / check (push) Successful in 15m7s
CI / deploy-infra (push) Has been skipped
CI / test (push) Successful in 14m30s
CI / clippy (push) Successful in 13m10s
CI / deploy (push) Has been skipped
CI / deploy (pull_request) Has been cancelled
CI / deploy-infra (pull_request) Has been cancelled
CI / check (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / clippy (pull_request) Has been cancelled
2026-04-25 20:18:22 +02:00
Sithies f2797a7424 refactor: separate backup into own workflow, fix cron separation
CI / check (push) Successful in 12m54s
CI / deploy-infra (push) Has been skipped
CI / test (push) Successful in 14m52s
CI / clippy (push) Successful in 16m12s
CI / deploy (push) Has been skipped
2026-04-25 20:14:12 +02:00
Sithies 72affaa0d8 feat: add Dockerfile, deploy pipeline, Ollama infra job, weekly cron
CI / check (push) Successful in 13m8s
CI / test (push) Successful in 13m23s
CI / clippy (push) Successful in 12m46s
CI / deploy-infra (push) Has been skipped
CI / deploy (push) Has been skipped
2026-04-25 19:22:51 +02:00
Sithies b6a5618f78 Fix
CI / check (push) Successful in 3m13s
CI / test (push) Successful in 3m53s
CI / clippy (push) Failing after 3m15s
CI / deploy (push) Has been skipped
2026-04-25 18:59:24 +02:00
Sithies fe148fda4e Rework ci
CI / check (push) Successful in 3m20s
CI / test (push) Successful in 3m52s
CI / clippy (push) Failing after 2m43s
CI / deploy (push) Has been skipped
2026-04-25 17:08:29 +02:00
14 changed files with 343 additions and 111 deletions
+45
View File
@@ -0,0 +1,45 @@
name: Backup
on:
schedule:
- cron: '0 2 * * *' # Täglich 02:00 UTC
jobs:
backup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.SEBAS }}
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PI_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p 10022 localhost >> ~/.ssh/known_hosts
- name: Pull backups from Pi
run: |
mkdir -p backup/data backup/config
scp -i ~/.ssh/deploy_key -P 10022 \
"deploy@localhost:/opt/nazarick/data/*.sqlite" \
backup/data/ || true
scp -i ~/.ssh/deploy_key -P 10022 \
deploy@localhost:/opt/nazarick/config/config.toml \
backup/config/config.toml || true
ssh -i ~/.ssh/deploy_key -p 10022 deploy@localhost \
'find /opt/nazarick/crates/*/config -type f -name "*.md"' | while read f; do
RELATIVE=${f#/opt/nazarick/}
mkdir -p "backup/$(dirname $RELATIVE)"
scp -i ~/.ssh/deploy_key -P 10022 \
"deploy@localhost:$f" "backup/$RELATIVE" || true
done
- name: Commit and push
run: |
git config user.name "Nazarick Backup Bot"
git config user.email "backup@nazarick"
git add backup/
git diff --staged --quiet || git commit -m "chore: daily backup $(date +%Y-%m-%d)"
git push origin master
+125
View File
@@ -0,0 +1,125 @@
name: CI
on:
push:
branches: [ '**' ]
pull_request:
branches: [ master ]
schedule:
- cron: '0 3 * * 1' # Montags 03:00 UTC → deploy-infra (Ollama update)
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo check --all-targets
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -- -D warnings
deploy:
runs-on: ubuntu-latest
needs: [ check, test, clippy ]
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Install cross-compilation tools
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v2
- name: Build ARM64
run: cargo build --release --target aarch64-unknown-linux-gnu
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PI_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p 10022 localhost >> ~/.ssh/known_hosts
- name: Copy binary to Pi
run: |
scp -i ~/.ssh/deploy_key -P 10022 \
target/aarch64-unknown-linux-gnu/release/nazarick \
deploy@localhost:/opt/nazarick/nazarick.new
- name: Copy Dockerfile to Pi
run: |
scp -i ~/.ssh/deploy_key -P 10022 \
Dockerfile \
deploy@localhost:/opt/nazarick/Dockerfile
- name: Copy config files dynamically to Pi
run: |
# shared config
scp -i ~/.ssh/deploy_key -P 10022 \
config/shared_core.md \
deploy@localhost:/opt/nazarick/config/shared_core.md
# Alle Agent-Config-Files dynamisch (soul_core.md, soul_personality.md etc.)
find crates/*/config -type f -name "*.md" | while read f; do
CRATE=$(echo "$f" | cut -d'/' -f1-3)
ssh -i ~/.ssh/deploy_key -p 10022 deploy@localhost "mkdir -p /opt/nazarick/$CRATE"
scp -i ~/.ssh/deploy_key -P 10022 "$f" "deploy@localhost:/opt/nazarick/$f"
done
- name: Build image and restart nazarick
run: |
ssh -i ~/.ssh/deploy_key -p 10022 deploy@localhost '
cd /opt/nazarick
mkdir -p target/release
mv nazarick.new target/release/nazarick
docker build -t nazarick:latest .
docker compose down nazarick || true
docker compose up -d nazarick
'
deploy-infra:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PI_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p 10022 localhost >> ~/.ssh/known_hosts
- name: Update Ollama + pull latest Gemma
run: |
ssh -i ~/.ssh/deploy_key -p 10022 deploy@localhost '
cd /opt/nazarick
docker compose pull ollama
docker compose up -d ollama
sleep 5
docker exec ollama ollama pull gemma4:e2b
docker compose restart nazarick
'
+45 -1
View File
@@ -2,7 +2,7 @@ name: CI
on:
push:
branches: [ master ]
branches: [ '**' ] # alle Branches
pull_request:
branches: [ master ]
@@ -32,3 +32,47 @@ jobs:
with:
components: clippy
- run: cargo clippy --all-features -- -D warnings
deploy:
runs-on: ubuntu-latest
needs: [ check, test, clippy ]
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Install cross-compilation tools
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu
- name: Build ARM64
run: |
cargo build --release --target aarch64-unknown-linux-gnu
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PI_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p 10022 localhost >> ~/.ssh/known_hosts
- name: Copy binary to Pi
run: |
scp -i ~/.ssh/deploy_key -P 10022 \
target/aarch64-unknown-linux-gnu/release/nazarick \
deploy@localhost:/opt/nazarick/nazarick.new
- name: Restart on Pi
run: |
ssh -i ~/.ssh/deploy_key -p 10022 deploy@localhost '
mv /opt/nazarick/nazarick.new /opt/nazarick/nazarick
cd /opt/nazarick
docker compose down || true
docker compose up -d
'
+1
View File
@@ -1,4 +1,5 @@
/target
/.claude
.env
*.key
config/private/
+19
View File
@@ -0,0 +1,19 @@
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Pfad spiegelt die Cargo-Ausgabestruktur:
# current_exe() = /app/target/release/nazarick
# 3× .parent() → /app/target/release → /app/target → /app (workspace_root) ✓
RUN mkdir -p /app/target/release
COPY target/aarch64-unknown-linux-gnu/release/nazarick /app/target/release/nazarick
VOLUME ["/app/config", "/app/data"]
EXPOSE 8765
CMD ["/app/target/release/nazarick"]
+15 -6
View File
@@ -2,18 +2,27 @@
# ─── Modelle ──────────────────────────────────────────────────────────────────
[models.default]
provider = "openai_compat"
url = "http://localhost:11434/v1"
model = "gemma3:2b"
api_key = "ollama"
skill_format = "xml"
[models.summary]
provider = "openai_compat"
url = "http://localhost:11434"
model = "gemma3:2b"
api_key = "ollama"
skill_format = "xml"
max_summary_tokens = 2000
[models.openrouter-llama]
provider = "openai_compat"
url = "https://openrouter.ai/api/v1"
model = "meta-llama/llama-3.3-70b-instruct"
skill_format = "tool_use"
api_key = "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"
[models.summary]
provider = "openai_compat"
url = "http://localhost:11434"
model = "llama3.1:8b"
max_summary_tokens = 5000
skill_format = "xml"
# ─── Chat ─────────────────────────────────────────────────────────────────────
[chat]
+3 -23
View File
@@ -1,5 +1,5 @@
use std::sync::Arc;
use nazarick_core::agent::base::BaseAgent;
use nazarick_core::agent::base::{AgentConfig, BaseAgent};
use nazarick_core::agent::skill_registry::SkillRegistry;
use nazarick_core::memory::Memory;
use nazarick_core::summarizer::Summarizer;
@@ -13,34 +13,14 @@ pub struct Lyra {
impl Lyra {
pub fn new(
agent_id: impl Into<String>,
shared_core_path: impl Into<String>,
soul_core_path: impl Into<String>,
config: AgentConfig,
llm: Box<dyn LlmProvider>,
registry: Arc<SkillRegistry>,
memory: Arc<dyn Memory>,
summarizer: Arc<dyn Summarizer>,
max_tokens: u32,
max_loops: u32,
history_window: usize,
summary_every: usize,
conversation_timeout_mins: u64,
) -> Self {
Self {
base: BaseAgent::new(
agent_id,
shared_core_path,
soul_core_path,
llm,
registry,
memory,
summarizer,
max_tokens,
max_loops,
history_window,
summary_every,
conversation_timeout_mins,
),
base: BaseAgent::new(config, llm, registry, memory, summarizer),
}
}
+32 -29
View File
@@ -13,6 +13,17 @@ use crate::agent::skill_registry::SkillRegistry;
use crate::memory::Memory;
use crate::summarizer::Summarizer;
pub struct AgentConfig {
pub agent_id: String,
pub shared_core_path: String,
pub soul_core_path: String,
pub max_tokens: u32,
pub max_loops: u32,
pub history_window: usize,
pub summary_every: usize,
pub conversation_timeout_mins: u64,
}
pub struct BaseAgent {
pub id: AgentId,
agent_id: String,
@@ -36,36 +47,28 @@ pub struct BaseAgent {
impl BaseAgent {
pub fn new(
agent_id: impl Into<String>,
shared_core_path: impl Into<String>,
soul_core_path: impl Into<String>,
config: AgentConfig,
llm: Box<dyn LlmProvider>,
registry: Arc<SkillRegistry>,
memory: Arc<dyn Memory>,
summarizer: Arc<dyn Summarizer>,
max_tokens: u32,
max_loops: u32,
history_window: usize,
summary_every: usize,
conversation_timeout_mins: u64,
) -> Self {
let skill_format = llm.skill_format();
let agent_id = agent_id.into();
Self {
id: AgentId::new_v4(),
agent_id: agent_id.clone(),
max_tokens,
max_loops,
history_window,
summary_every,
conversation_timeout_mins,
agent_id: config.agent_id.clone(),
max_tokens: config.max_tokens,
max_loops: config.max_loops,
history_window: config.history_window,
summary_every: config.summary_every,
conversation_timeout_mins: config.conversation_timeout_mins,
conversation_id: 0,
messages_since_summary: 0,
prompt_builder: PromptBuilder::new(
&agent_id,
shared_core_path,
soul_core_path,
&config.agent_id,
config.shared_core_path,
config.soul_core_path,
),
skill_executor: SkillExecutor::new(registry.clone(), skill_format.clone()),
skill_format,
@@ -243,17 +246,17 @@ impl BaseAgent {
continue;
}
if let Some(skill_name) = Self::parse_skill_info(&clean_raw) {
if let Some(skill) = self.registry.get(&skill_name) {
let details = format!(
"[Skill-Details für '{}']\n{}",
skill_name,
skill.details()
);
loop_context.push(Message::assistant(&clean_raw));
loop_context.push(Message::user(&details));
continue;
}
if let Some(skill_name) = Self::parse_skill_info(&clean_raw)
&& let Some(skill) = self.registry.get(&skill_name)
{
let details = format!(
"[Skill-Details für '{}']\n{}",
skill_name,
skill.details()
);
loop_context.push(Message::assistant(&clean_raw));
loop_context.push(Message::user(&details));
continue;
}
let (clean, feedback) = self.skill_executor.process(
@@ -120,11 +120,7 @@ impl SkillExecutor {
let mut calls = Vec::new();
let mut clean = response.to_string();
loop {
let start = match clean.find("<skill name=\"") {
Some(s) => s,
None => break,
};
while let Some(start) = clean.find("<skill name=\"") {
let end = match clean[start..].find("</skill>") {
Some(e) => start + e,
None => {
+8 -6
View File
@@ -1,5 +1,6 @@
// nazarick-core/src/llm/traits.rs
use std::str::FromStr;
use crate::types::Result;
use crate::llm::types::{LlmRequest, LlmResponse};
@@ -13,13 +14,14 @@ pub enum SkillFormat {
None,
}
impl SkillFormat {
/// Parsed aus config.toml String
pub fn from_str(s: &str) -> Self {
impl FromStr for SkillFormat {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"tool_use" => Self::ToolUse,
"none" => Self::None,
_ => Self::Xml, // default
"tool_use" => Ok(Self::ToolUse),
"none" => Ok(Self::None),
_ => Ok(Self::Xml),
}
}
}
+1 -1
View File
@@ -250,7 +250,7 @@ fn split_message(text: &str) -> Vec<String> {
.unwrap_or(safe_max);
chunks.push(remaining[..cut].trim().to_string());
remaining = &remaining[cut..].trim_start();
remaining = remaining[cut..].trim_start();
}
if !remaining.is_empty() {
+22 -17
View File
@@ -10,6 +10,7 @@ use tokio::sync::Mutex;
use tower_http::trace::TraceLayer;
use tracing::info;
use nazarick_core::agent::base::AgentConfig;
use nazarick_core::agent::skill_registry::SkillRegistry;
use nazarick_core::llm::{LlmProvider, SkillFormat};
use api::llm::openai_compat::OpenAiCompatProvider;
@@ -26,7 +27,7 @@ use skills as _;
fn build_provider(model_cfg: &ModelConfig) -> Box<dyn LlmProvider> {
let skill_format = model_cfg.skill_format
.as_deref()
.map(SkillFormat::from_str)
.map(|s| s.parse::<SkillFormat>().unwrap_or(SkillFormat::Xml))
.unwrap_or(SkillFormat::Xml);
match model_cfg.provider.as_str() {
@@ -107,34 +108,38 @@ async fn main() -> anyhow::Result<()> {
info!("Memory geladen");
let mut sebas = Sebas::new(
"sebas_tian",
"config/shared_core.md",
"crates/sebas-tian/config/soul_core.md",
AgentConfig {
agent_id: "sebas_tian".to_string(),
shared_core_path: "config/shared_core.md".to_string(),
soul_core_path: "crates/sebas-tian/config/soul_core.md".to_string(),
max_tokens: sebas_cfg.max_tokens,
max_loops: sebas_cfg.max_loops,
history_window: sebas_cfg.history_window,
summary_every: sebas_cfg.summary_every,
conversation_timeout_mins: sebas_cfg.conversation_timeout_mins,
},
build_provider(sebas_model),
registry.clone(),
sebas_memory,
summarizer.clone(),
sebas_cfg.max_tokens,
sebas_cfg.max_loops,
sebas_cfg.history_window,
sebas_cfg.summary_every,
sebas_cfg.conversation_timeout_mins,
);
sebas.init().await?;
let mut lyra = Lyra::new(
"lyra",
"config/shared_core.md",
"crates/lyra/config/soul_core.md",
AgentConfig {
agent_id: "lyra".to_string(),
shared_core_path: "config/shared_core.md".to_string(),
soul_core_path: "crates/lyra/config/soul_core.md".to_string(),
max_tokens: lyra_cfg.max_tokens,
max_loops: lyra_cfg.max_loops,
history_window: lyra_cfg.history_window,
summary_every: lyra_cfg.summary_every,
conversation_timeout_mins: lyra_cfg.conversation_timeout_mins,
},
build_provider(lyra_model),
registry.clone(),
lyra_memory,
summarizer.clone(),
lyra_cfg.max_tokens,
lyra_cfg.max_loops,
lyra_cfg.history_window,
lyra_cfg.summary_every,
lyra_cfg.conversation_timeout_mins,
);
lyra.init().await?;
+3 -23
View File
@@ -1,5 +1,5 @@
use std::sync::Arc;
use nazarick_core::agent::base::BaseAgent;
use nazarick_core::agent::base::{AgentConfig, BaseAgent};
use nazarick_core::agent::skill_registry::SkillRegistry;
use nazarick_core::memory::Memory;
use nazarick_core::summarizer::Summarizer;
@@ -13,34 +13,14 @@ pub struct Sebas {
impl Sebas {
pub fn new(
agent_id: impl Into<String>,
shared_core_path: impl Into<String>,
soul_core_path: impl Into<String>,
config: AgentConfig,
llm: Box<dyn LlmProvider>,
registry: Arc<SkillRegistry>,
memory: Arc<dyn Memory>,
summarizer: Arc<dyn Summarizer>,
max_tokens: u32,
max_loops: u32,
history_window: usize,
summary_every: usize,
conversation_timeout_mins: u64,
) -> Self {
Self {
base: BaseAgent::new(
agent_id,
shared_core_path,
soul_core_path,
llm,
registry,
memory,
summarizer,
max_tokens,
max_loops,
history_window,
summary_every,
conversation_timeout_mins,
),
base: BaseAgent::new(config, llm, registry, memory, summarizer),
}
}
+23
View File
@@ -0,0 +1,23 @@
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
network_mode: host
volumes:
- /opt/nazarick/ollama:/root/.ollama
nazarick:
image: nazarick:latest
container_name: nazarick
restart: unless-stopped
network_mode: host
depends_on:
- ollama
volumes:
# Binary-Pfad: /app/target/release/nazarick
# 3× parent() → workspace_root = /app ✓
- /opt/nazarick/target:/app/target
- /opt/nazarick/config:/app/config
- /opt/nazarick/data:/app/data
working_dir: /app