[+] Docker
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
/.git
|
||||||
|
/.github
|
||||||
|
.env
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
FROM rust:1-slim-bookworm AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
RUN cargo build --release --locked
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates git nano openssh-client vim \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& useradd --uid 10001 --create-home --home-dir /home/refray --shell /usr/sbin/nologin refray \
|
||||||
|
&& mkdir -p /data \
|
||||||
|
&& chown -R refray:refray /data
|
||||||
|
|
||||||
|
COPY --from=build /app/target/release/refray /usr/local/bin/refray
|
||||||
|
|
||||||
|
USER refray
|
||||||
|
WORKDIR /data
|
||||||
|
ENV XDG_CONFIG_HOME=/data/config \
|
||||||
|
XDG_CACHE_HOME=/data/cache
|
||||||
|
|
||||||
|
EXPOSE 8787
|
||||||
|
ENTRYPOINT ["refray"]
|
||||||
|
CMD ["serve", "--listen", "0.0.0.0:8787"]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# refray
|
# refray
|
||||||
|
|
||||||
A tool to keep ALL of your repos in sync across ALL git platforms, while being able to work from any one of them.
|
A tool to keep your repos in sync across all git platforms, while being able to work from everywhere all at once.
|
||||||
|
|
||||||
Created becasue github is so unusable and unreliable and I want to leave, but I don't want to leave the community behind.
|
Created becasue github is so unusable and unreliable and I want to leave, but I don't want to leave the community behind.
|
||||||
|
|
||||||
@@ -22,6 +22,26 @@ Supported platforms: GitHub, GitLab, Gitea, Forgejo
|
|||||||
|
|
||||||
Go to the [releases page](https://github.com/MaigoLabs/refray/releases), find the latest release, and download the appropriate binary for your platform.
|
Go to the [releases page](https://github.com/MaigoLabs/refray/releases), find the latest release, and download the appropriate binary for your platform.
|
||||||
|
|
||||||
|
### Option 3. Docker Compose
|
||||||
|
|
||||||
|
Run config wizard:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose run --rm refray config
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the webhook receiver as a service:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
To edit config manually:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose run --rm --entrypoint nano refray /data/config/refray/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
## Configure
|
## Configure
|
||||||
|
|
||||||
Run the interactive configuration wizard:
|
Run the interactive configuration wizard:
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
refray:
|
||||||
|
build: .
|
||||||
|
image: refray:local
|
||||||
|
command: ["serve", "--listen", "0.0.0.0:8787"]
|
||||||
|
ports:
|
||||||
|
- "8787:8787"
|
||||||
|
volumes:
|
||||||
|
- refray-data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
refray-data:
|
||||||
|
name: refray-data
|
||||||
+25
-2
@@ -37,7 +37,7 @@ struct ParsedProfileUrl {
|
|||||||
namespace: String,
|
namespace: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_config_wizard(path: &Path) -> Result<()> {
|
pub fn run_config_wizard(path: &Path) -> Result<ConfigWizardOutcome> {
|
||||||
let existing_config = path.exists();
|
let existing_config = path.exists();
|
||||||
let mut config = Config::load_or_default(path)?;
|
let mut config = Config::load_or_default(path)?;
|
||||||
let theme = ColorfulTheme::default();
|
let theme = ColorfulTheme::default();
|
||||||
@@ -85,7 +85,17 @@ pub fn run_config_wizard(path: &Path) -> Result<()> {
|
|||||||
style("saved").green().bold(),
|
style("saved").green().bold(),
|
||||||
style(path.display()).cyan()
|
style(path.display()).cyan()
|
||||||
);
|
);
|
||||||
Ok(())
|
let run_full_sync_now = prompt_run_full_sync_now_styled(&config, &theme)?;
|
||||||
|
Ok(ConfigWizardOutcome {
|
||||||
|
config,
|
||||||
|
run_full_sync_now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ConfigWizardOutcome {
|
||||||
|
pub config: Config,
|
||||||
|
pub run_full_sync_now: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -389,6 +399,19 @@ fn prompt_wizard_action_styled(theme: &ColorfulTheme) -> Result<WizardAction> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_run_full_sync_now_styled(config: &Config, theme: &ColorfulTheme) -> Result<bool> {
|
||||||
|
if config.mirrors.is_empty() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
Confirm::with_theme(theme)
|
||||||
|
.with_prompt("Run full sync now?")
|
||||||
|
.default(false)
|
||||||
|
.interact()
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_sync_group_styled(config: &mut Config, theme: &ColorfulTheme) -> Result<bool> {
|
fn edit_sync_group_styled(config: &mut Config, theme: &ColorfulTheme) -> Result<bool> {
|
||||||
if config.mirrors.is_empty() {
|
if config.mirrors.is_empty() {
|
||||||
println!("{}", style("No sync groups to edit.").yellow());
|
println!("{}", style("No sync groups to edit.").yellow());
|
||||||
|
|||||||
+8
-1
@@ -146,7 +146,14 @@ fn main() -> Result<()> {
|
|||||||
let config_path = cli.config.unwrap_or_else(default_config_path);
|
let config_path = cli.config.unwrap_or_else(default_config_path);
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Config => interactive::run_config_wizard(&config_path),
|
Command::Config => {
|
||||||
|
let outcome = interactive::run_config_wizard(&config_path)?;
|
||||||
|
if outcome.run_full_sync_now {
|
||||||
|
sync_all(&outcome.config, SyncOptions::default())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Command::Sync(command) => {
|
Command::Sync(command) => {
|
||||||
let config = load_config(&config_path)?;
|
let config = load_config(&config_path)?;
|
||||||
sync_all(
|
sync_all(
|
||||||
|
|||||||
@@ -219,6 +219,43 @@ fn wizard_starts_existing_config_at_sync_group_menu() {
|
|||||||
assert!(!output.contains("Profile/org URL:"));
|
assert!(!output.contains("Profile/org URL:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wizard_can_ask_to_run_full_sync_after_config() {
|
||||||
|
let config = Config {
|
||||||
|
sites: Vec::new(),
|
||||||
|
mirrors: vec![MirrorConfig {
|
||||||
|
name: "sync-1".to_string(),
|
||||||
|
endpoints: Vec::new(),
|
||||||
|
create_missing: true,
|
||||||
|
visibility: Visibility::Private,
|
||||||
|
allow_force: false,
|
||||||
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
|
}],
|
||||||
|
webhook: None,
|
||||||
|
};
|
||||||
|
let mut reader = Cursor::new(b"y\n".as_slice());
|
||||||
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
let run_full_sync =
|
||||||
|
prompt_run_full_sync_now_with_io(&config, &mut reader, &mut output).unwrap();
|
||||||
|
|
||||||
|
assert!(run_full_sync);
|
||||||
|
let output = String::from_utf8(output).unwrap();
|
||||||
|
assert!(output.contains("Run full sync now? [y/N]:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wizard_skips_full_sync_prompt_without_sync_groups() {
|
||||||
|
let mut reader = Cursor::new(b"".as_slice());
|
||||||
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
let run_full_sync =
|
||||||
|
prompt_run_full_sync_now_with_io(&Config::default(), &mut reader, &mut output).unwrap();
|
||||||
|
|
||||||
|
assert!(!run_full_sync);
|
||||||
|
assert!(output.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wizard_edits_existing_sync_group_from_menu() {
|
fn wizard_edits_existing_sync_group_from_menu() {
|
||||||
let config = Config {
|
let config = Config {
|
||||||
|
|||||||
@@ -41,6 +41,22 @@ where
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prompt_run_full_sync_now_with_io<R, W>(
|
||||||
|
config: &Config,
|
||||||
|
reader: &mut R,
|
||||||
|
writer: &mut W,
|
||||||
|
) -> Result<bool>
|
||||||
|
where
|
||||||
|
R: BufRead,
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
if config.mirrors.is_empty() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_bool(reader, writer, "Run full sync now?", false)
|
||||||
|
}
|
||||||
|
|
||||||
fn add_sync_group<R, W>(reader: &mut R, writer: &mut W, config: &mut Config) -> Result<()>
|
fn add_sync_group<R, W>(reader: &mut R, writer: &mut W, config: &mut Config) -> Result<()>
|
||||||
where
|
where
|
||||||
R: BufRead,
|
R: BufRead,
|
||||||
|
|||||||
Reference in New Issue
Block a user