Skip to content

Commit

Permalink
Add delete file and upload mod-settings, optimise runtime images
Browse files Browse the repository at this point in the history
  • Loading branch information
circlesabound committed Apr 22, 2024
1 parent 6c81aa0 commit 4518066
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ DISCORD_INTEGRATION=false
########

AGENT_WS_PORT=5463
LOG_LEVEL=info
LOG_LEVEL=error,agent=info,mgmt_server=info

########
# Traefik reverse proxy
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2021 John
Copyright 2024 John

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion agent.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y ca-certificates
COPY --from=builder /usr/src/app/target/release /usr/local/bin
COPY --from=builder /usr/src/app/target/release/agent /usr/local/bin/agent
ENTRYPOINT [ "/usr/local/bin/agent" ]
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ services:
- MGMT_SERVER_WS_ADDRESS=${MGMT_SERVER_BIND}
- MGMT_SERVER_WS_PORT
- ROCKET_ADDRESS=${MGMT_SERVER_BIND}
- ROCKET_LIMITS={bytes="2 MiB"}
- ROCKET_LOG_LEVEL=critical
- ROCKET_PORT=${MGMT_SERVER_PORT}
- RPROXY_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion mgmt-server.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y ca-certificates
COPY --from=builder /usr/src/app/target/release /usr/local/bin
COPY --from=builder /usr/src/app/target/release/mgmt-server /usr/local/bin/mgmt-server
COPY --from=web-builder /app/web/dist/web /app/web/dist/web
ENTRYPOINT [ "/usr/local/bin/mgmt-server" ]
16 changes: 14 additions & 2 deletions openapi/mgmt-server-rest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ paths:
parameters:
- name: savefile_id
in: path
description: Name of the savefile to be uploaded to the server
description: Name of the savefile to be retrieved
required: true
schema:
type: string
Expand All @@ -142,7 +142,19 @@ paths:
schema:
type: string
format: binary
post:
delete:
summary: Delete the savefile from the server
parameters:
- name: savefile_id
in: path
description: Name of the savefile to be delete
required: true
schema:
type: string
responses:
'200':
description: Ok
put:
summary: Pushes a savefile to the server for use
parameters:
- name: savefile_id
Expand Down
36 changes: 35 additions & 1 deletion src/agent/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ impl AgentController {
self.save_create(save_name, operation_id).await
}

AgentRequest::SaveDelete(save_name) => {
self.save_delete(save_name, operation_id).await
}

AgentRequest::SaveGet(save_name) => {
self.save_get(save_name, operation_id).await
}
Expand All @@ -326,7 +330,7 @@ impl AgentController {
}

AgentRequest::SaveSet(save_name, bytes) => {
todo!()
self.save_set(save_name, bytes, operation_id).await;
}

// **************
Expand Down Expand Up @@ -1010,6 +1014,20 @@ impl AgentController {
}
}

async fn save_delete(&self, save_name: String, operation_id: OperationId) {
match util::saves::exists_savefile(&save_name).await {
Ok(true) => {
if let Err(e) = util::saves::delete_savefile(&save_name).await {
self.reply_failed(AgentOutMessage::Error(format!("Failed to delete save: {:?}", e)), operation_id).await;
} else {
self.reply_success(AgentOutMessage::Ok, operation_id).await;
}
},
Ok(false) => self.reply_failed(AgentOutMessage::Error(format!("Savefile with name {} does not exist", save_name)), operation_id).await,
Err(e) => self.reply_failed(AgentOutMessage::Error(format!("Failed to list saves: {:?}", e)), operation_id).await,
}
}

async fn save_get(&self, save_name: String, operation_id: OperationId) {
match util::saves::get_savefile(&save_name).await {
Ok(Some(savebytes)) => {
Expand Down Expand Up @@ -1051,6 +1069,22 @@ impl AgentController {
}
}

async fn save_set(&self, save_name: String, savebytes: SaveBytes, operation_id: OperationId) {
match util::saves::exists_savefile(&save_name).await {
Ok(false) => {
if let Err(e) = util::saves::set_savefile(&save_name, savebytes).await {
self.reply_failed(AgentOutMessage::Error(format!("Failed to set savefile with name `{}`: {:?}", &save_name, e)), operation_id).await
} else {
self.reply_success(AgentOutMessage::Ok, operation_id).await
}
},
Ok(true) => self.reply_failed(AgentOutMessage::Error(format!("Savefile with name {} already exists", &save_name)), operation_id).await,
Err(e) => self.reply_failed(
AgentOutMessage::Error(format!("Failed to read saves: {:?}", e)),
operation_id).await,
}
}

async fn mod_list_get(&self, operation_id: OperationId) {
match ModManager::read_or_apply_default().await {
Ok(m) => {
Expand Down
34 changes: 33 additions & 1 deletion src/agent/util/saves.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};

use fctrl::schema::{Save, SaveBytes};
use log::warn;
use log::{error, info, warn};
use tokio::fs;

use crate::{consts::*, error::Result};
Expand All @@ -10,6 +10,24 @@ pub fn get_savefile_path(save_name: impl AsRef<str>) -> PathBuf {
SAVEFILE_DIR.join(format!("{}.zip", save_name.as_ref()))
}

pub async fn delete_savefile(save_name: impl AsRef<str>) -> Result<()> {
let path = get_savefile_path(save_name.as_ref());
match fs::remove_file(path).await {
Ok(()) => {
info!("Successfully deleted savefile `{}`", save_name.as_ref());
Ok(())
},
Err(e) => {
error!("Failed to delete savefile `{}`: {:?}", save_name.as_ref(), e);
Err(e.into())
},
}
}

pub async fn exists_savefile(save_name: impl AsRef<str>) -> Result<bool> {
Ok(list_savefiles().await?.into_iter().find(|s| s.name == save_name.as_ref()).is_some())
}

pub async fn get_savefile(save_name: impl AsRef<str>) -> Result<Option<SaveBytes>> {
if !SAVEFILE_DIR.is_dir() {
return Ok(None);
Expand Down Expand Up @@ -43,6 +61,20 @@ pub async fn list_savefiles() -> Result<Vec<Save>> {
Ok(ret)
}

pub async fn set_savefile(save_name: impl AsRef<str>, savebytes: SaveBytes) -> Result<()> {
let bytes_length = savebytes.bytes.len();
match fs::write(get_savefile_path(save_name.as_ref()), savebytes.bytes).await {
Ok(()) => {
info!("Successfully set savefile `{}`, wrote {} bytes", save_name.as_ref(), bytes_length);
Ok(())
},
Err(e) => {
error!("Failed to set savefile `{}`: {:?}", save_name.as_ref(), e);
Err(e.into())
}
}
}

fn parse_from_path<P: AsRef<Path>>(path: P) -> Result<Save> {
if let Some(ext) = path.as_ref().extension() {
if ext == "zip" {
Expand Down
33 changes: 29 additions & 4 deletions src/mgmt-server/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,7 @@ impl AgentApiClient {
.await
}

pub async fn save_create(
&self,
savefile_name: String,
) -> Result<(OperationId, impl Stream<Item = Event> + Unpin)> {
pub async fn save_create(&self, savefile_name: String) -> Result<(OperationId, impl Stream<Item = Event> + Unpin)> {
if savefile_name.trim().is_empty() {
return Err(Error::BadRequest("Empty savefile name".to_owned()));
}
Expand All @@ -148,6 +145,20 @@ impl AgentApiClient {
ack_or_timeout(sub, Duration::from_millis(500), id).await
}

pub async fn save_delete(&self, savefile_name: String) -> Result<()> {
if savefile_name.trim().is_empty() {
return Err(Error::BadRequest("Empty savefile name".to_owned()));
}

let request = AgentRequest::SaveDelete(savefile_name);
let (_id, sub) = self.send_request_and_subscribe(request).await?;

response_or_timeout(sub, Duration::from_millis(10000), |r| match r.content {
AgentOutMessage::Ok => Ok(()),
m => Err(default_message_handler(m)),
}).await
}

pub async fn save_get(&self, savefile_name: String) -> Result<(OperationId, impl Stream<Item = Event> + Unpin)> {
if savefile_name.trim().is_empty() {
return Err(Error::BadRequest("Empty savefile name".to_owned()));
Expand All @@ -159,6 +170,20 @@ impl AgentApiClient {
ack_or_timeout(sub, Duration::from_millis(500), id).await
}

pub async fn save_put(&self, savefile_name: String, savebytes: SaveBytes) -> Result<()> {
if savefile_name.trim().is_empty() {
return Err(Error::BadRequest("Empty savefile name".to_owned()));
}

let request = AgentRequest::SaveSet(savefile_name, savebytes);
let (_id, sub) = self.send_request_and_subscribe(request).await?;

response_or_timeout(sub, Duration::from_millis(10000), |r| match r.content {
AgentOutMessage::Ok => Ok(()),
m => Err(default_message_handler(m)),
}).await
}

pub async fn save_list(&self) -> Result<Vec<Save>> {
let request = AgentRequest::SaveList;
let (_id, sub) = self.send_request_and_subscribe(request).await?;
Expand Down
4 changes: 3 additions & 1 deletion src/mgmt-server/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use auth::{AuthnManager, AuthnProvider, AuthzManager};
use events::*;
use futures::{pin_mut, StreamExt};
use log::{debug, error, info};
use rocket::{async_trait, catchers, fairing::Fairing, fs::FileServer, routes};
use rocket::{async_trait, catchers, data::ToByteUnit, fairing::Fairing, fs::FileServer, routes};

use crate::{
auth::UserIdentity, clients::AgentApiClient, db::{Cf, Db, Record}, discord::DiscordClient, events::broker::EventBroker, link_download::LinkDownloadManager, rpc::RpcHandler, ws::WebSocketServer
Expand Down Expand Up @@ -157,6 +157,8 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
routes::server::upgrade_install,
routes::server::get_install,
routes::server::get_savefile,
routes::server::delete_savefile,
routes::server::put_savefile,
routes::server::get_savefiles,
routes::server::get_adminlist,
routes::server::put_adminlist,
Expand Down
28 changes: 26 additions & 2 deletions src/mgmt-server/routes/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use std::{

use factorio_file_parser::ModSettings;
use fctrl::schema::{
mgmt_server_rest::*, FactorioVersion, ModSettingsBytes, RconConfig, SecretsObject, ServerSettingsConfig, ServerStartSaveFile, ServerStatus
mgmt_server_rest::*, FactorioVersion, ModSettingsBytes, RconConfig, SaveBytes, SecretsObject, ServerSettingsConfig, ServerStartSaveFile, ServerStatus
};
use rocket::serde::json::Json;
use rocket::{delete, serde::json::Json};
use rocket::{get, post, put};
use rocket::{http::Status, State};

Expand Down Expand Up @@ -134,6 +134,15 @@ pub async fn get_savefiles(
Ok(Json(ret))
}

#[delete("/server/savefiles/<id>")]
pub async fn delete_savefile(
_a: AuthorizedUser,
agent_client: &State<Arc<AgentApiClient>>,
id: String,
) -> Result<()> {
agent_client.save_delete(id).await
}

#[get("/server/savefiles/<id>")]
pub async fn get_savefile<'a>(
_a: AuthorizedUser,
Expand All @@ -144,6 +153,21 @@ pub async fn get_savefile<'a>(
Ok(LinkDownloadResponder::new(link_id))
}

#[put("/server/savefiles/<id>", data = "<body>")]
pub async fn put_savefile(
_a: AuthorizedUser,
agent_client: &State<Arc<AgentApiClient>>,
id: String,
body: Vec<u8>,
) -> Result<()> {
let savebytes = SaveBytes {
multipart_seqnum: None,
bytes: body,
};
agent_client.save_put(id, savebytes).await?;
Ok(())
}

#[get("/server/config/adminlist")]
pub async fn get_adminlist(
_a: AuthorizedUser,
Expand Down
2 changes: 2 additions & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub enum AgentRequest {
///
/// **This is a long-running operation.**
SaveCreate(String),
/// Delete the save file from the server with the requested name
SaveDelete(String),
/// Gets the save file zip from the server
SaveGet(String),
/// Get a list of the save files present on the server.
Expand Down
17 changes: 17 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"private": true,
"dependencies": {
"@angular-material-components/file-input": "^16.0.1",
"@angular/animations": "~17.0.8",
"@angular/cdk": "^17.0.4",
"@angular/common": "~17.0.8",
Expand Down Expand Up @@ -72,5 +73,15 @@
"protractor": "~7.0.0",
"ts-node": "~10.9.2",
"typescript": "~5.2.0"
},
"overrides": {
"@angular-material-components/file-input": {
"@angular/cdk": ">=16.0.0",
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@angular/forms": ">=16.0.0",
"@angular/material": ">=16.0.0",
"@angular/platform-browser": ">=16.0.0"
}
}
}
2 changes: 2 additions & 0 deletions web/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { NgxMatFileInputModule } from '@angular-material-components/file-input';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
Expand Down Expand Up @@ -87,6 +88,7 @@ export const BEARER_AUTH_INTERCEPTOR_PROVIDER: Provider = {
},
}
}),
NgxMatFileInputModule,
ClickOutsideModule,
],
providers: [
Expand Down
Loading

0 comments on commit 4518066

Please sign in to comment.