├── .editorconfig ├── .env.local ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── blank_issue.md │ ├── defect.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── docs.yml │ ├── go.yml │ ├── nix.yml │ └── rust.yml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── .idx └── dev.nix ├── LICENSE ├── Makefile ├── README.md ├── bin └── .gitkeep ├── crates ├── client │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs └── example │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── main.rs ├── default.nix ├── examples ├── .example_env └── data_aggregation_workflow.yaml ├── fixtures-code ├── process-compose-chain-exit.yaml ├── process-compose-chain.yaml ├── process-compose-circular.yaml ├── process-compose-non-circular.yaml ├── process-compose-scale.yaml ├── process-compose-shutdown-inorder.yaml ├── process-compose-transitive-dep.yaml ├── process-compose-with-extends.yaml └── process-compose-with-log.yaml ├── fixtures ├── process-compose-chain-arrow.yaml ├── process-compose-chain-rhombus.yaml ├── process-compose-chain-with-errors.yaml ├── process-compose-many-for-one.yaml ├── process-compose-odd-then-even.yaml └── process-compose-one-for-many.yaml ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── imgs ├── btc.wallet.qr.png ├── diagram.png ├── output.png ├── swagger.png └── tui.png ├── issues ├── issue_102 │ └── process-compose.yaml ├── issue_107 │ ├── after.4h.alloc_space.txt │ ├── after.4h.txt │ └── process-compose.yaml ├── issue_111 │ ├── process-compose-probes.yaml │ └── process-compose.yaml ├── issue_120 │ └── process-compose.yaml ├── issue_156 │ └── process-compose.yaml ├── issue_166 │ └── process-compose.yaml ├── issue_177 │ ├── process-compose.override.yml │ └── process-compose.yml ├── issue_189 │ └── process-compose.yaml ├── issue_191 │ └── process-compose.yaml ├── issue_200 │ └── process-compose.yaml ├── issue_214 │ └── process-compose.yaml ├── issue_237 │ ├── logger.sh │ └── process-compose.yml ├── issue_238 │ └── process-compose.yaml ├── issue_239 │ └── process-compose.yaml ├── issue_258 │ ├── devbox.json │ ├── devbox.lock │ └── process-compose.yaml ├── issue_274 │ └── process-compose.json ├── issue_278 │ ├── db │ │ ├── db.sh │ │ └── process-compose.yaml │ └── server │ │ ├── process-compose.yaml │ │ └── server.sh ├── issue_280 │ └── process-compose.yaml ├── issue_316 │ └── process-compose.yaml ├── issue_49 │ ├── bad_script.sh │ └── process-compose.yaml ├── issue_57 │ ├── long_log.py │ ├── process-compose.yaml │ └── too_chatty.py ├── issue_72 │ ├── clientA │ │ └── .gitkeep │ └── process-compose.yaml └── issue_84 │ └── process-compose.yaml ├── process-compose-win.yaml ├── process-compose.override.yaml ├── process-compose.yaml ├── schemas └── process-compose-schema.json ├── scripts └── get-pc.sh ├── shell.nix ├── src ├── admitter │ ├── admitter.go │ ├── disabled.go │ ├── namespace.go │ └── namespace_test.go ├── api │ ├── pc_api.go │ ├── routes.go │ ├── server.go │ ├── types.go │ └── ws_api.go ├── app │ ├── daemon.go │ ├── pc_string.go │ ├── pc_string_test.go │ ├── proc_opts.go │ ├── process.go │ ├── project_interface.go │ ├── project_opts.go │ ├── project_runner.go │ ├── project_runner_test.go │ ├── system_test.go │ └── table.go ├── client │ ├── client.go │ ├── common.go │ ├── logs.go │ ├── processes.go │ ├── project.go │ ├── restart.go │ ├── scale_process.go │ ├── start.go │ ├── status.go │ └── stop.go ├── cmd │ ├── 0-init.go │ ├── attach.go │ ├── docs.go │ ├── down.go │ ├── get.go │ ├── info.go │ ├── is_ready.go │ ├── list.go │ ├── logs.go │ ├── ports.go │ ├── process.go │ ├── project.go │ ├── project_runner.go │ ├── project_runner_unix.go │ ├── project_runner_windows.go │ ├── restart.go │ ├── root.go │ ├── run.go │ ├── scale.go │ ├── start.go │ ├── state.go │ ├── stop.go │ ├── stop_test.go │ ├── truncate.go │ ├── up.go │ ├── update.go │ └── version.go ├── command │ ├── command.go │ ├── command_wrapper.go │ ├── command_wrapper_pty.go │ ├── commander.go │ ├── config.go │ ├── mock_command.go │ ├── noise_maker.go │ ├── stopper_unix.go │ └── stopper_windows.go ├── config │ ├── Flags.go │ ├── color.go │ ├── config.go │ ├── files.go │ ├── settings.go │ ├── styles.go │ ├── themes.go │ └── themes │ │ ├── catppuccin-frappe-theme.yaml │ │ ├── catppuccin-latte-theme.yaml │ │ ├── catppuccin-macchiato-theme.yaml │ │ ├── catppuccin-mocha-theme.yaml │ │ ├── cobalt-theme.yaml │ │ ├── default-theme.yaml │ │ ├── light-theme.yaml │ │ ├── material-theme.yaml │ │ ├── monokai-theme.yaml │ │ └── onedark-theme.yaml ├── docs │ ├── docs.go │ ├── swagger.json │ └── swagger.yaml ├── health │ ├── exec_checker.go │ ├── health_checks.go │ ├── probe.go │ └── probe_test.go ├── loader │ ├── loader.go │ ├── loader_options.go │ ├── loader_options_test.go │ ├── loader_test.go │ ├── merger.go │ ├── merger_test.go │ ├── mutators.go │ ├── mutators_test.go │ ├── validators.go │ └── validators_test.go ├── main.go ├── pclog │ ├── colortracker.go │ ├── log_observer_connector.go │ ├── logger_facade.go │ ├── logger_interface.go │ ├── logs_observer.go │ ├── nil_logger.go │ ├── process_log_buffer.go │ └── unique_id.go ├── templater │ ├── templater.go │ └── templater_test.go ├── tui │ ├── actions.go │ ├── debug.go │ ├── dialog.go │ ├── help-dialog.go │ ├── log-operations.go │ ├── log-viewer.go │ ├── main-grid.go │ ├── namespace-selector.go │ ├── proc-editor.go │ ├── proc-info-form.go │ ├── proc-starter.go │ ├── proc-table.go │ ├── proc-table_test.go │ ├── procstate_sorter.go │ ├── scale-form.go │ ├── search-form.go │ ├── stat-table.go │ ├── styles.go │ ├── theme-selector.go │ ├── tui_option.go │ └── view.go ├── types │ ├── deprecation.go │ ├── logger.go │ ├── process.go │ ├── process_test.go │ ├── processcondition_string.go │ ├── project.go │ ├── project_state.go │ ├── project_test.go │ └── restartpolicy_string.go └── updater │ └── update_checker.go ├── test_loop.bash ├── test_loop.ps1 └── www ├── docs ├── cli │ ├── process-compose.md │ ├── process-compose_attach.md │ ├── process-compose_completion.md │ ├── process-compose_completion_bash.md │ ├── process-compose_completion_fish.md │ ├── process-compose_completion_powershell.md │ ├── process-compose_completion_zsh.md │ ├── process-compose_down.md │ ├── process-compose_info.md │ ├── process-compose_list.md │ ├── process-compose_process.md │ ├── process-compose_process_get.md │ ├── process-compose_process_list.md │ ├── process-compose_process_logs.md │ ├── process-compose_process_logs_truncate.md │ ├── process-compose_process_ports.md │ ├── process-compose_process_restart.md │ ├── process-compose_process_scale.md │ ├── process-compose_process_start.md │ ├── process-compose_process_stop.md │ ├── process-compose_project.md │ ├── process-compose_project_is-ready.md │ ├── process-compose_project_state.md │ ├── process-compose_project_update.md │ ├── process-compose_run.md │ ├── process-compose_up.md │ └── process-compose_version.md ├── client.md ├── configuration.md ├── contributing.md ├── health.md ├── img │ └── favicon.ico ├── index.md ├── installation.md ├── intro.md ├── launcher.md ├── logging.md ├── merge.md ├── sponsors.md └── tui.md └── mkdocs.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | 11 | [*.go] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.yaml] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.html] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | [*.md] 24 | indent_style = space 25 | trim_trailing_whitespace = false 26 | 27 | [Makefile] 28 | indent_style = tab 29 | indent_size = 4 30 | -------------------------------------------------------------------------------- /.env.local: -------------------------------------------------------------------------------- 1 | LOCAL_ENV_VAR1=FOO 2 | LOCAL_ENV_VAR2=BAR 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: [F1bonacc1] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Blank Issue 3 | about: Create an issue with a blank template. 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/defect.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Defect Report 3 | about: Report a bug found in Process Compose 4 | labels: 🐞 bug 5 | --- 6 | 7 | ## Defect 8 | 9 | Make sure that these boxes are checked before submitting your issue -- thank you! 10 | 11 | - [ ] Included the relevant configuration snippet 12 | - [ ] Included the relevant process-compose log (log location: `process-compose info`) 13 | - [ ] Included a [Minimal, Complete, and Verifiable example] (https://stackoverflow.com/help/mcve) 14 | 15 | #### Version of `process-compose`: 16 | 17 | #### OS environment: 18 | 19 | #### Steps or code to reproduce the issue: 20 | 21 | #### Expected result: 22 | 23 | #### Actual result: 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Request a feature for the Process Compose 4 | labels: 🎉 enhancement 5 | --- 6 | 7 | ## Feature Request 8 | 9 | #### Use Case: 10 | 11 | #### Proposed Change: 12 | 13 | #### Who Benefits From The Change(s)? 14 | 15 | #### Alternative Approaches 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - docs 7 | paths: 8 | - "www/**/*" 9 | - ".github/workflows/docs.yml" 10 | pull_request: 11 | paths: 12 | - "www/**/*" 13 | - ".github/workflows/docs.yml" 14 | 15 | permissions: 16 | contents: write 17 | jobs: 18 | deploy: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions/setup-python@v4 23 | with: 24 | python-version: 3.x 25 | - uses: actions/cache@v4 26 | with: 27 | key: ${{ github.ref }} 28 | path: .cache 29 | - run: pip install mkdocs-material 30 | - run: pip install pillow cairosvg 31 | 32 | - name: Build docs 33 | if: ${{ github.event_name == 'pull_request' }} 34 | run: mkdocs build 35 | working-directory: ./www 36 | 37 | - name: Deploy docs to GitHub Pages 38 | if: ${{ github.event_name != 'pull_request' }} 39 | run: mkdocs gh-deploy --force 40 | working-directory: ./www 41 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: 1.23 19 | 20 | - name: Build 21 | run: go build -v -o process-compose ./src 22 | 23 | - name: Test 24 | run: go test -v ./src/... 25 | 26 | - name: Run golangci-lint 27 | uses: golangci/golangci-lint-action@v6 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Install Nix 14 | uses: cachix/install-nix-action@v31 15 | - name: Nix version 16 | run: nix --version 17 | - name: Nix build 18 | run: nix build -L .#packages.x86_64-linux.process-compose 19 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Install Rust 14 | uses: actions-rust-lang/setup-rust-toolchain@v1 15 | with: 16 | toolchain: stable 17 | override: true 18 | - name: Rust build 19 | run: cd crates/client && cargo build --release 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | .vscode 3 | .idea 4 | bin/* 5 | *.log 6 | *.log.gz 7 | .env 8 | # local build output via go build 9 | process-compose 10 | 11 | # nix build .#process-compose output 12 | result 13 | 14 | dist/ 15 | /exports 16 | www/venv 17 | www/.cache 18 | 19 | Cargo.lock 20 | target 21 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | # Enable specific linter 4 | # https://golangci-lint.run/usage/linters/#enabled-by-default 5 | enable: 6 | - asasalint 7 | - asciicheck 8 | - bidichk 9 | # - bodyclose 10 | - canonicalheader 11 | # - containedctx 12 | - contextcheck 13 | - copyloopvar 14 | # - cyclop 15 | - decorder 16 | # - depguard 17 | # - dogsled 18 | # - dupl 19 | - dupword 20 | - durationcheck 21 | # - err113 22 | - errcheck 23 | - errchkjson 24 | - errname 25 | # - errorlint 26 | # - execinquery 27 | # - exhaustive 28 | # - exhaustruct 29 | # - exportloopref 30 | - fatcontext 31 | # - forbidigo 32 | # - forcetypeassert 33 | # - funlen 34 | # - gci 35 | - ginkgolinter 36 | - gocheckcompilerdirectives 37 | # - gochecknoglobals 38 | # - gochecknoinits 39 | - gochecksumtype 40 | # - gocognit 41 | # - goconst 42 | # - gocritic 43 | - gocyclo 44 | # - godot 45 | # - godox 46 | # - gofmt 47 | # - gofumpt 48 | - goheader 49 | # - goimports 50 | # - gomoddirectives 51 | - gomodguard 52 | # - goprintffuncname 53 | # - gosec 54 | - gosimple 55 | - gosmopolitan 56 | - govet 57 | - grouper 58 | - importas 59 | # - inamedparam 60 | - ineffassign 61 | # - interfacebloat 62 | # - intrange 63 | # - ireturn 64 | # - lll 65 | - loggercheck 66 | - maintidx 67 | # - makezero 68 | - mirror 69 | - misspell 70 | # - mnd 71 | # - musttag 72 | - nakedret 73 | # - nestif 74 | - nilerr 75 | # - nilnil 76 | # - nlreturn 77 | # - noctx 78 | - nolintlint 79 | # - nonamedreturns 80 | - nosprintfhostport 81 | # - paralleltest 82 | # - perfsprint 83 | # - prealloc 84 | # - predeclared 85 | - promlinter 86 | - protogetter 87 | - reassign 88 | # - revive 89 | - rowserrcheck 90 | - sloglint 91 | - spancheck 92 | - sqlclosecheck 93 | - staticcheck 94 | # - stylecheck 95 | # - tagalign 96 | - usetesting 97 | - testableexamples 98 | - testifylint 99 | # - testpackage 100 | # - thelper 101 | - tparallel 102 | - unconvert 103 | # - unparam 104 | # - unused 105 | - usestdlibvars 106 | # - varnamelen 107 | - wastedassign 108 | # - whitespace 109 | # - wrapcheck 110 | # - wsl 111 | - zerologlint 112 | -------------------------------------------------------------------------------- /.idx/dev.nix: -------------------------------------------------------------------------------- 1 | # To learn more about how to use Nix to configure your environment 2 | # see: https://developers.google.com/idx/guides/customize-idx-env 3 | { pkgs, ... }: { 4 | # Which nixpkgs channel to use. 5 | channel = "unstable"; # or "unstable" 6 | 7 | # Use https://search.nixos.org/packages to find packages 8 | packages = [ 9 | pkgs.go 10 | pkgs.gnumake 11 | pkgs.zsh 12 | # pkgs.python311 13 | # pkgs.python311Packages.pip 14 | # pkgs.nodejs_20 15 | # pkgs.nodePackages.nodemon 16 | ]; 17 | 18 | # Sets environment variables in the workspace 19 | env = {}; 20 | idx = { 21 | # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" 22 | extensions = [ 23 | "golang.go" 24 | "wesbos.theme-cobalt2" 25 | ]; 26 | 27 | # Enable previews 28 | previews = { 29 | enable = true; 30 | previews = { 31 | # web = { 32 | # # Example: run "npm run dev" with PORT set to IDX's defined port for previews, 33 | # # and show it in IDX's web preview panel 34 | # command = ["npm" "run" "dev"]; 35 | # manager = "web"; 36 | # env = { 37 | # # Environment variables to set for your server 38 | # PORT = "$PORT"; 39 | # }; 40 | # }; 41 | }; 42 | }; 43 | 44 | # Workspace lifecycle hooks 45 | workspace = { 46 | # Runs when a workspace is first created 47 | onCreate = { 48 | # Example: install JS dependencies from NPM 49 | go-tidy = "go tidy"; 50 | }; 51 | # Runs when the workspace is (re)started 52 | onStart = { 53 | # Example: start a background task to watch and re-build backend code 54 | # watch-backend = "npm run watch-backend"; 55 | }; 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/bin/.gitkeep -------------------------------------------------------------------------------- /crates/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "process-compose-client" 3 | version = "1.64.1" 4 | description = "Client for Process Compose via OpenAPI and/or project file" 5 | license = "Apache-2.0" 6 | edition = "2021" 7 | include = [ 8 | "src/**", 9 | "Cargo.toml", 10 | "../../src/docs/swagger.json", 11 | "../../schemas/process-compose-schema.json", 12 | "../../README.md", 13 | ] 14 | 15 | [lib] 16 | crate-type = ["rlib"] 17 | 18 | [dependencies] 19 | serde_json = { version = "^1.0", default-features = false } 20 | openapiv3 = { version = "^2", default-features = false } 21 | 22 | progenitor = { version = "^0.10", default-features = false, optional = true } 23 | prettyplease = { version = "^0.2.24", optional = true } 24 | syn = { version = "^2.0.80", optional = true } 25 | schemars = { version = "^0.8.*" } 26 | 27 | typify = { version = "^0.4", default-features = false, optional = true } 28 | 29 | [features] 30 | default = ["progenitor", "typify"] 31 | 32 | typify = ["dep:typify", "dep:prettyplease"] 33 | 34 | progenitor = ["dep:progenitor", "dep:prettyplease", "dep:syn"] 35 | -------------------------------------------------------------------------------- /crates/client/README.md: -------------------------------------------------------------------------------- 1 | 2 | Build time utility to get Rust Process Compose interface: 3 | 4 | - as raw OpenAPI schema or with `progenitor` client. 5 | - as raw project config JSON schema or with `typify` builder. 6 | -------------------------------------------------------------------------------- /crates/client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! provides generator to use in build.rs, with default `progenitor` provider 2 | use openapiv3::OpenAPI; 3 | use std::sync::OnceLock; 4 | 5 | /// Raw OpenAPI spec string 6 | pub const OPENAPI_JSON_STRING: &str = include_str!("../../../src/docs/swagger.json"); 7 | 8 | /// Raw Process Compose config file schema string 9 | pub const CONFIG_SCHEMA_JSON_STRING: &str = 10 | include_str!("../../../schemas/process-compose-schema.json"); 11 | 12 | static OPENAPI: OnceLock = OnceLock::new(); 13 | 14 | static CONFIG_SCHEMA: OnceLock = OnceLock::new(); 15 | 16 | /// OpenAPI spec parsed 17 | pub fn openapi() -> &'static OpenAPI { 18 | OPENAPI.get_or_init(|| serde_json::from_str(OPENAPI_JSON_STRING).unwrap()); 19 | OPENAPI.get().unwrap() 20 | } 21 | 22 | /// Parsed Process Compose config file schema 23 | pub fn config_schema() -> &'static schemars::schema::RootSchema { 24 | CONFIG_SCHEMA.get_or_init(|| serde_json::from_str(CONFIG_SCHEMA_JSON_STRING).unwrap()); 25 | CONFIG_SCHEMA.get().unwrap() 26 | } 27 | 28 | /// Use this to get storngly typed config file builder and parser 29 | #[cfg(feature = "typify")] 30 | pub fn typify_pretty(maybe_config: Option) -> String { 31 | use typify::{TypeSpace, TypeSpaceSettings}; 32 | let mut type_space = maybe_config 33 | .unwrap_or_else(|| TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true))); 34 | type_space.add_root_schema(config_schema().clone()).unwrap(); 35 | prettyplease::unparse(&syn::parse2::(type_space.to_stream()).unwrap()) 36 | } 37 | 38 | /// Use this to get strongly typed HTTP client 39 | #[cfg(feature = "progenitor")] 40 | pub fn progenitor_pretty(maybe_config: Option) -> String { 41 | let mut generator = maybe_config.unwrap_or_default(); 42 | let tokens = generator.generate_tokens(openapi()).unwrap(); 43 | let ast = syn::parse2(tokens).unwrap(); 44 | prettyplease::unparse(&ast) 45 | } 46 | -------------------------------------------------------------------------------- /crates/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | reqwest = { version = "0.12", default-features = false, features = [ 9 | "json", 10 | "stream", 11 | ] } 12 | serde = { version = "1.0", default-features = false, features = ["derive"]} 13 | tokio = { version = "1.41", features = ["macros", "rt-multi-thread"] } 14 | serde_json = { version = "1.0", default-features = false } 15 | 16 | progenitor-client = { version = "0.10", default-features = false, optional = false} 17 | 18 | [build-dependencies] 19 | process-compose-client = { path = "../client", default-features = false, features = ["progenitor", "typify"] } 20 | -------------------------------------------------------------------------------- /crates/example/build.rs: -------------------------------------------------------------------------------- 1 | /// add this file to you crate if you want to use the generated clients/configs 2 | fn main() { 3 | // openapi client 4 | let client = process_compose_client::progenitor_pretty(None); 5 | let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf(); 6 | out_file.push("client.rs"); 7 | std::fs::write(out_file, client).unwrap(); 8 | 9 | // config schema builder/parser 10 | let config = process_compose_client::typify_pretty(None); 11 | let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf(); 12 | out_file.push("config.rs"); 13 | std::fs::write(out_file, config).unwrap(); 14 | } 15 | -------------------------------------------------------------------------------- /crates/example/src/main.rs: -------------------------------------------------------------------------------- 1 | // includes generated code 2 | include!(concat!(env!("OUT_DIR"), "/client.rs")); 3 | include!(concat!(env!("OUT_DIR"), "/config.rs")); 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | // we just compile it to check for compile errors 8 | let client = crate::Client::new("locahost:8080"); 9 | if let Ok(response) = client.get_process_info("process-compose").await { 10 | let _name = &response.name; 11 | unreachable!("errors on bad url"); 12 | } 13 | 14 | let _config = crate::Project::builder().processes(Processes(<_>::default())); 15 | 16 | println!("Compiles!") 17 | } 18 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { buildGoModule, config, lib, pkgs, installShellFiles, date, commit }: 2 | 3 | let pkg = "github.com/f1bonacc1/process-compose/src/config"; 4 | in 5 | buildGoModule rec { 6 | pname = "process-compose"; 7 | version = "1.64.1"; 8 | go = pkgs.go_1_23; 9 | env.CGO_ENABLED = 0; 10 | 11 | src = lib.cleanSource ./.; 12 | ldflags = [ 13 | "-X ${pkg}.Version=v${version}" 14 | "-X ${pkg}.Date=${date}" 15 | "-X ${pkg}.Commit=${commit}" 16 | "-s" 17 | "-w" 18 | ]; 19 | 20 | nativeBuildInputs = [ installShellFiles ]; 21 | 22 | vendorHash = "sha256-qkfJo+QGqcqiZMLuWbj0CpgRWxbqTu6DGAW8pBu4O/0="; 23 | #vendorHash = lib.fakeHash; 24 | 25 | postInstall = '' 26 | mv $out/bin/{src,process-compose} 27 | 28 | installShellCompletion --cmd process-compose \ 29 | --bash <($out/bin/process-compose completion bash) \ 30 | --zsh <($out/bin/process-compose completion zsh) \ 31 | --fish <($out/bin/process-compose completion fish) 32 | ''; 33 | 34 | meta = with lib; { 35 | description = "A simple and flexible scheduler and orchestrator to manage non-containerized applications"; 36 | homepage = "https://github.com/F1bonacc1/process-compose"; 37 | changelog = "https://github.com/F1bonacc1/process-compose/releases/tag/v${version}"; 38 | license = licenses.asl20; 39 | mainProgram = "process-compose"; 40 | }; 41 | 42 | doCheck = false; # it takes ages to run the tests 43 | } 44 | -------------------------------------------------------------------------------- /examples/.example_env: -------------------------------------------------------------------------------- 1 | VERSION='1.2.3' 2 | DB_USER='USERNAME' 3 | DB_PASSWORD='VERY_STRONG_PASSWORD' 4 | WAIT_SEC=60 -------------------------------------------------------------------------------- /fixtures-code/process-compose-chain-exit.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | depends_on: 6 | process2: 7 | condition: process_completed_successfully 8 | 9 | process2: 10 | command: "echo process2" 11 | availability: 12 | restart: "on_failure" 13 | depends_on: 14 | process3: 15 | condition: process_completed_successfully 16 | 17 | process3: 18 | command: "echo process3" 19 | availability: 20 | restart: "on_failure" 21 | backoff_seconds: 2 22 | depends_on: 23 | process4: 24 | condition: process_completed_successfully 25 | 26 | process4: 27 | command: "echo process4" 28 | availability: 29 | restart: "on_failure" 30 | depends_on: 31 | process5: 32 | condition: process_completed_successfully 33 | 34 | process5: 35 | command: "echo process5" 36 | availability: 37 | restart: "on_failure" 38 | backoff_seconds: 2 39 | depends_on: 40 | process6: 41 | condition: process_completed_successfully 42 | 43 | process6: 44 | command: "echo process6" 45 | availability: 46 | restart: "on_failure" 47 | depends_on: 48 | process7: 49 | condition: process_completed_successfully 50 | 51 | process7: 52 | command: "echo process7" 53 | availability: 54 | restart: "on_failure" 55 | depends_on: 56 | process8: 57 | condition: process_completed_successfully 58 | 59 | process8: 60 | command: "exit 42" 61 | availability: 62 | restart: "exit_on_failure" 63 | backoff_seconds: 2 64 | 65 | 66 | 67 | environment: 68 | - 'ABC=222' 69 | 70 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-chain.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | depends_on: 6 | process2: 7 | condition: process_completed_successfully 8 | 9 | process2: 10 | command: "echo process2" 11 | working_dir: "/tmp" 12 | availability: 13 | restart: "on_failure" 14 | depends_on: 15 | process3: 16 | condition: process_completed_successfully 17 | 18 | process3: 19 | command: "echo process3" 20 | working_dir: "../../" 21 | availability: 22 | restart: "on_failure" 23 | backoff_seconds: 2 24 | depends_on: 25 | process4: 26 | condition: process_completed_successfully 27 | 28 | process4: 29 | command: "echo process4" 30 | availability: 31 | restart: "on_failure" 32 | depends_on: 33 | process5: 34 | condition: process_completed_successfully 35 | 36 | process5: 37 | command: "echo process5" 38 | availability: 39 | restart: "on_failure" 40 | backoff_seconds: 2 41 | depends_on: 42 | process6: 43 | condition: process_completed_successfully 44 | 45 | process6: 46 | command: "echo process6" 47 | availability: 48 | restart: "on_failure" 49 | depends_on: 50 | process7: 51 | condition: process_completed_successfully 52 | 53 | process7: 54 | command: "echo process7" 55 | availability: 56 | restart: "on_failure" 57 | depends_on: 58 | process8: 59 | condition: process_completed_successfully 60 | 61 | process8: 62 | command: "echo process8" 63 | availability: 64 | restart: "on_failure" 65 | backoff_seconds: 2 66 | 67 | 68 | 69 | environment: 70 | - 'ABC=222' 71 | 72 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-circular.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | depends_on: 6 | process2: 7 | condition: process_completed_successfully 8 | 9 | process2: 10 | command: "echo process2" 11 | depends_on: 12 | process1: 13 | condition: process_completed_successfully 14 | 15 | process3: 16 | command: "echo process3" 17 | depends_on: 18 | process1: 19 | condition: process_completed_successfully 20 | 21 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-non-circular.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | 6 | process3: 7 | command: "echo process3" 8 | depends_on: 9 | process1: 10 | condition: process_completed_successfully 11 | 12 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-scale.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1 ; sleep 2" 5 | replicas: 2 6 | depends_on: 7 | process2: 8 | condition: process_completed_successfully 9 | 10 | process2: 11 | command: "echo process2; sleep 2" 12 | availability: 13 | restart: "on_failure" 14 | depends_on: 15 | process3: 16 | condition: process_completed_successfully 17 | 18 | process3: 19 | command: "echo process3; sleep 2" 20 | availability: 21 | restart: "on_failure" 22 | backoff_seconds: 2 23 | 24 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-shutdown-inorder.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | log_level: debug 4 | log_length: 1000 5 | 6 | processes: 7 | procA: 8 | command: | 9 | trap 'echo "A: exit"' SIGTERM 10 | echo "A: starting" 11 | sleep 3 12 | 13 | procB: 14 | command: | 15 | trap 'echo "B: exit"' SIGTERM 16 | echo "B: starting" 17 | sleep 3 18 | depends_on: 19 | procA: 20 | condition: process_started 21 | 22 | procC: 23 | command: | 24 | trap 'echo "C: exit"' SIGTERM 25 | echo "C: starting" 26 | sleep 3 27 | depends_on: 28 | procB: 29 | condition: process_started 30 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-transitive-dep.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | log_level: debug 4 | log_length: 1000 5 | 6 | processes: 7 | procA: 8 | command: "exit 1" 9 | 10 | procB: 11 | command: echo "I shouldn't run" 12 | depends_on: 13 | procA: 14 | condition: process_completed_successfully 15 | 16 | procC: 17 | command: echo "I shouldn't run" 18 | depends_on: 19 | procB: 20 | condition: process_completed_successfully 21 | 22 | 23 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-with-extends.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | extends: process-compose-chain.yaml 3 | processes: 4 | process1: 5 | command: "echo extending" 6 | -------------------------------------------------------------------------------- /fixtures-code/process-compose-with-log.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: info 3 | processes: 4 | process1: 5 | command: "echo process1" 6 | depends_on: 7 | process2: 8 | condition: process_completed_successfully 9 | 10 | process2: 11 | command: "echo 'process2 is removing the log'" 12 | availability: 13 | restart: "on_failure" 14 | # max_restarts: 3 15 | depends_on: 16 | process3: 17 | condition: process_completed 18 | 19 | process3: 20 | command: "echo 'process3 error' >&2 && exit 1" 21 | depends_on: 22 | process4: 23 | condition: process_completed_successfully 24 | 25 | process4: 26 | command: "echo process4" 27 | availability: 28 | restart: "on_failure" 29 | depends_on: 30 | process5: 31 | condition: process_completed_successfully 32 | 33 | process5: 34 | command: "echo 9" 35 | availability: 36 | restart: "on_failure" 37 | backoff_seconds: 1 38 | # max_restarts: 3 39 | depends_on: 40 | process6: 41 | condition: process_completed_successfully 42 | 43 | process6: 44 | command: "echo process6" 45 | availability: 46 | restart: "on_failure" 47 | log_location: ./pc.proc6.log-test.log 48 | depends_on: 49 | process7: 50 | condition: process_completed_successfully 51 | 52 | process7: 53 | command: "echo process7" 54 | availability: 55 | restart: "on_failure" 56 | depends_on: 57 | process8: 58 | condition: process_completed_successfully 59 | 60 | process8: 61 | command: "echo process8" 62 | availability: 63 | restart: "on_failure" 64 | backoff_seconds: 2 65 | 66 | environment: 67 | - 'ABC=222' 68 | 69 | log_location: ./pc.log-test.log 70 | -------------------------------------------------------------------------------- /fixtures/process-compose-chain-arrow.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | depends_on: 6 | process2: 7 | condition: process_completed_successfully 8 | 9 | process2: 10 | command: "echo process2" 11 | availability: 12 | restart: "on_failure" 13 | depends_on: 14 | process3: 15 | condition: process_completed_successfully 16 | 17 | process3: 18 | command: "echo process3" 19 | availability: 20 | restart: "on_failure" 21 | backoff_seconds: 2 22 | depends_on: 23 | process4: 24 | condition: process_completed_successfully 25 | 26 | process4: 27 | command: "echo process4" 28 | availability: 29 | restart: "on_failure" 30 | depends_on: 31 | process5: 32 | condition: process_completed_successfully 33 | 34 | process5: 35 | command: "echo process5" 36 | availability: 37 | restart: "on_failure" 38 | backoff_seconds: 2 39 | depends_on: 40 | process6: 41 | condition: process_completed_successfully 42 | 43 | process6: 44 | command: "echo process6" 45 | availability: 46 | restart: "on_failure" 47 | depends_on: 48 | process7: 49 | condition: process_completed_successfully 50 | process8: 51 | condition: process_completed_successfully 52 | 53 | process7: 54 | command: "echo process7" 55 | availability: 56 | restart: "on_failure" 57 | depends_on: 58 | process8: 59 | condition: process_completed_successfully 60 | 61 | process8: 62 | command: "echo process8" 63 | availability: 64 | restart: "on_failure" 65 | backoff_seconds: 2 66 | 67 | environment: 68 | - 'ABC=222' 69 | 70 | -------------------------------------------------------------------------------- /fixtures/process-compose-chain-rhombus.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process1: 4 | command: "echo process1" 5 | depends_on: 6 | process2: 7 | condition: process_completed_successfully 8 | 9 | process2: 10 | command: "echo process2" 11 | availability: 12 | restart: "on_failure" 13 | depends_on: 14 | process3: 15 | condition: process_completed_successfully 16 | 17 | process3: 18 | command: "echo process3" 19 | availability: 20 | restart: "on_failure" 21 | backoff_seconds: 2 22 | depends_on: 23 | process4: 24 | condition: process_completed_successfully 25 | 26 | process4: 27 | command: "echo process4" 28 | availability: 29 | restart: "on_failure" 30 | depends_on: 31 | process5: 32 | condition: process_completed_successfully 33 | 34 | process5: 35 | command: "echo process5" 36 | availability: 37 | restart: "on_failure" 38 | backoff_seconds: 2 39 | depends_on: 40 | process6: 41 | condition: process_completed_successfully 42 | process7: 43 | condition: process_completed_successfully 44 | 45 | process6: 46 | command: "echo process6" 47 | availability: 48 | restart: "on_failure" 49 | depends_on: 50 | process8: 51 | condition: process_completed_successfully 52 | 53 | process7: 54 | command: "echo process7" 55 | availability: 56 | restart: "on_failure" 57 | depends_on: 58 | process8: 59 | condition: process_completed_successfully 60 | 61 | process8: 62 | command: "echo process8" 63 | availability: 64 | restart: "on_failure" 65 | backoff_seconds: 2 66 | 67 | 68 | 69 | environment: 70 | - 'ABC=222' 71 | 72 | -------------------------------------------------------------------------------- /fixtures/process-compose-chain-with-errors.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: "undefined" 3 | processes: 4 | process1: 5 | command: "echo process1" 6 | depends_on: 7 | process2: 8 | condition: process_completed_successfully 9 | 10 | process2: 11 | command: "echo process2" 12 | availability: 13 | restart: "on_failure" 14 | depends_on: 15 | process3: 16 | condition: process_completed_successfully 17 | 18 | process3: 19 | command: "echo 'process3 error' >&2 && exit 1" 20 | depends_on: 21 | process4: 22 | condition: process_completed_successfully 23 | 24 | process4: 25 | command: "echo process4" 26 | availability: 27 | restart: "on_failure" 28 | depends_on: 29 | process5: 30 | condition: process_completed 31 | 32 | process5: 33 | command: "echo 'process5 error' >&2 && exit 1" 34 | availability: 35 | restart: "on_failure" 36 | backoff_seconds: 1 37 | max_restarts: 1 38 | depends_on: 39 | process6: 40 | condition: process_completed_successfully 41 | 42 | process6: 43 | command: "echo process6" 44 | availability: 45 | restart: "always" 46 | backoff_seconds: 2 47 | max_restarts: 1 48 | depends_on: 49 | process7: 50 | condition: process_completed_successfully 51 | 52 | process7: 53 | command: "echo process7" 54 | availability: 55 | restart: "on_failure" 56 | depends_on: 57 | process8: 58 | condition: process_completed_successfully 59 | 60 | process8: 61 | command: "echo process8" 62 | availability: 63 | restart: "on_failure" 64 | backoff_seconds: 2 65 | 66 | 67 | environment: 68 | - 'ABC=222' 69 | 70 | -------------------------------------------------------------------------------- /fixtures/process-compose-many-for-one.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process2: 4 | command: "echo process2" 5 | availability: 6 | restart: "on_failure" 7 | backoff_seconds: 2 8 | depends_on: 9 | process1: 10 | condition: process_completed_successfully 11 | 12 | process1: 13 | command: "echo process1" 14 | availability: 15 | restart: "on_failure" 16 | 17 | process4: 18 | command: "echo process4" 19 | availability: 20 | restart: "on_failure" 21 | backoff_seconds: 2 22 | depends_on: 23 | process1: 24 | condition: process_completed_successfully 25 | 26 | process3: 27 | command: "echo process3" 28 | availability: 29 | restart: "on_failure" 30 | depends_on: 31 | process1: 32 | condition: process_completed_successfully 33 | 34 | process6: 35 | command: "echo process6" 36 | availability: 37 | restart: "on_failure" 38 | backoff_seconds: 2 39 | depends_on: 40 | process1: 41 | condition: process_completed_successfully 42 | 43 | process5: 44 | command: "echo process5" 45 | availability: 46 | restart: "on_failure" 47 | depends_on: 48 | process1: 49 | condition: process_completed_successfully 50 | 51 | process8: 52 | command: "echo process8" 53 | availability: 54 | restart: "on_failure" 55 | backoff_seconds: 2 56 | depends_on: 57 | process1: 58 | condition: process_completed_successfully 59 | 60 | process7: 61 | command: "echo process7" 62 | availability: 63 | restart: "on_failure" 64 | depends_on: 65 | process1: 66 | condition: process_completed_successfully 67 | 68 | environment: 69 | - 'ABC=222' 70 | 71 | -------------------------------------------------------------------------------- /fixtures/process-compose-odd-then-even.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process2: 4 | command: "echo process2" 5 | availability: 6 | restart: "on_failure" 7 | backoff_seconds: 2 8 | depends_on: 9 | process1: 10 | condition: process_completed_successfully 11 | 12 | process1: 13 | command: "echo process1" 14 | availability: 15 | restart: "on_failure" 16 | 17 | process4: 18 | command: "echo process4" 19 | availability: 20 | restart: "on_failure" 21 | backoff_seconds: 2 22 | depends_on: 23 | process3: 24 | condition: process_completed_successfully 25 | 26 | process3: 27 | command: "echo process3" 28 | availability: 29 | restart: "on_failure" 30 | 31 | process6: 32 | command: "echo process6" 33 | availability: 34 | restart: "on_failure" 35 | backoff_seconds: 2 36 | depends_on: 37 | process5: 38 | condition: process_completed_successfully 39 | 40 | process5: 41 | command: "echo process5" 42 | availability: 43 | restart: "on_failure" 44 | 45 | process8: 46 | command: "echo process8" 47 | availability: 48 | restart: "on_failure" 49 | backoff_seconds: 2 50 | depends_on: 51 | process7: 52 | condition: process_completed_successfully 53 | 54 | process7: 55 | command: "echo process7" 56 | availability: 57 | restart: "on_failure" 58 | 59 | environment: 60 | - 'ABC=222' 61 | 62 | -------------------------------------------------------------------------------- /fixtures/process-compose-one-for-many.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | process2: 4 | command: "echo process2" 5 | availability: 6 | restart: "on_failure" 7 | backoff_seconds: 2 8 | depends_on: 9 | process1: 10 | condition: process_completed_successfully 11 | process3: 12 | condition: process_completed_successfully 13 | process4: 14 | condition: process_completed_successfully 15 | process5: 16 | condition: process_completed_successfully 17 | process6: 18 | condition: process_completed_successfully 19 | process7: 20 | condition: process_completed_successfully 21 | process8: 22 | condition: process_completed_successfully 23 | 24 | process1: 25 | command: "echo process1" 26 | 27 | process4: 28 | command: "echo process4" 29 | 30 | 31 | process3: 32 | command: "echo process3" 33 | 34 | 35 | process6: 36 | command: "echo process6" 37 | 38 | 39 | process5: 40 | command: "echo process5" 41 | 42 | 43 | process8: 44 | command: "echo process8" 45 | 46 | process7: 47 | command: "echo process7" 48 | 49 | environment: 50 | - 'ABC=222' 51 | 52 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1745079344, 24 | "narHash": "sha256-4gDQISoNjfKTlg/5xn1H5qEdjjge7Tb9ziJUO3xkIkk=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "651302ced0db8c7df5c7ba007780f1473a28f469", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "master", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = 3 | "Process Compose is like docker-compose, but for orchestrating a suite of processes, not containers."; 4 | 5 | # Nixpkgs / NixOS version to use. 6 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/master"; 7 | inputs.flake-utils.url = "github:numtide/flake-utils"; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | let 11 | mkPackage = pkgs: pkgs.callPackage ./default.nix { 12 | #version = self.shortRev or "dirty"; 13 | date = self.lastModifiedDate; 14 | commit = self.shortRev or "dirty"; 15 | }; 16 | in 17 | (flake-utils.lib.eachDefaultSystem (system: let 18 | pkgs = nixpkgs.legacyPackages.${system}; 19 | in { 20 | packages.process-compose = mkPackage pkgs; 21 | defaultPackage = self.packages."${system}".process-compose; 22 | apps.process-compose = flake-utils.lib.mkApp { 23 | drv = self.packages."${system}".process-compose; 24 | }; 25 | apps.default = self.apps."${system}".process-compose; 26 | checks.default = self.packages."${system}".process-compose.overrideAttrs (prev: { 27 | doCheck = true; 28 | nativeBuildInputs = prev.nativeBuildInputs ++ (with pkgs; [python3]); 29 | }); 30 | devShells.default = import ./shell.nix { inherit pkgs; }; 31 | }) 32 | ) // { 33 | overlays.default = final: prev: { 34 | process-compose = mkPackage final; 35 | }; 36 | } 37 | ; 38 | } 39 | -------------------------------------------------------------------------------- /imgs/btc.wallet.qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/imgs/btc.wallet.qr.png -------------------------------------------------------------------------------- /imgs/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/imgs/diagram.png -------------------------------------------------------------------------------- /imgs/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/imgs/output.png -------------------------------------------------------------------------------- /imgs/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/imgs/swagger.png -------------------------------------------------------------------------------- /imgs/tui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/imgs/tui.png -------------------------------------------------------------------------------- /issues/issue_102/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: info 3 | log_length: 300 4 | 5 | processes: 6 | watcher: 7 | command: "sleep 1 && echo SUCCESS && sleep 9" 8 | log_location: ./watcher.log 9 | log_configuration: 10 | flush_each_line: true 11 | readiness_probe: 12 | exec: 13 | command: "grep -q SUCCESS ./watcher.log" 14 | initial_delay_seconds: 1 15 | period_seconds: 2 16 | timeout_seconds: 2 17 | success_threshold: 1 18 | failure_threshold: 100 19 | 20 | wait4it: 21 | command: "echo watcher is healthy" 22 | depends_on: 23 | watcher: 24 | condition: process_healthy 25 | 26 | pc_log: 27 | command: "tail -f -n100 process-compose-${USER}.log" 28 | working_dir: "/tmp" 29 | -------------------------------------------------------------------------------- /issues/issue_107/after.4h.alloc_space.txt: -------------------------------------------------------------------------------- 1 | > go tool pprof -alloc_space heap.out 2 | File: process-compose 3 | Type: alloc_space 4 | Time: Dec 2, 2023 at 2:32am (IST) 5 | Duration: 30.01s, Total samples = 206.33MB 6 | Entering interactive mode (type "help" for commands, "o" for options) 7 | (pprof) top 8 | Showing nodes accounting for 199.44MB, 96.66% of 206.33MB total 9 | Dropped 72 nodes (cum <= 1.03MB) 10 | Showing top 10 nodes out of 26 11 | flat flat% sum% cum cum% 12 | 187.04MB 90.65% 90.65% 187.04MB 90.65% github.com/rivo/tview.(*TextView).parseAhead 13 | 5.90MB 2.86% 93.51% 5.90MB 2.86% strings.(*Builder).WriteString (inline) 14 | 3MB 1.45% 94.97% 3.50MB 1.70% github.com/rivo/tview.step 15 | 3MB 1.45% 96.42% 196.44MB 95.21% github.com/rivo/tview.(*TextView).Draw 16 | 0.50MB 0.24% 96.66% 3.50MB 1.70% github.com/rivo/tview.(*Table).Draw 17 | 0 0% 96.66% 2MB 0.97% github.com/InVisionApp/go-health/v2.(*Health).startRunner.func1 18 | 0 0% 96.66% 2MB 0.97% github.com/InVisionApp/go-health/v2.(*Health).startRunner.func2 19 | 0 0% 96.66% 200.44MB 97.15% github.com/f1bonacc1/process-compose/src/cmd.startTui 20 | 0 0% 96.66% 140.93MB 68.30% github.com/f1bonacc1/process-compose/src/tui.(*pcView).updateLogs.(*Application).QueueUpdateDraw.func4 21 | 0 0% 96.66% 59.51MB 28.84% github.com/f1bonacc1/process-compose/src/tui.(*pcView).updateTable.(*Application).QueueUpdateDraw.func4 22 | (pprof) 23 | -------------------------------------------------------------------------------- /issues/issue_107/after.4h.txt: -------------------------------------------------------------------------------- 1 | > go tool pprof heap.out 2 | File: process-compose 3 | Type: inuse_space 4 | Time: Dec 2, 2023 at 2:32am (IST) 5 | Duration: 30.01s, Total samples = 1.52MB 6 | Entering interactive mode (type "help" for commands, "o" for options) 7 | (pprof) top 8 | Showing nodes accounting for -528.06kB, 34.02% of 1552.40kB total 9 | Showing top 10 nodes out of 30 10 | flat flat% sum% cum cum% 11 | -528.17kB 34.02% 34.02% -528.17kB 34.02% regexp.(*bitState).reset 12 | 512.17kB 32.99% 1.03% 512.17kB 32.99% internal/profile.(*Profile).postDecode 13 | -512.06kB 32.99% 34.02% -512.06kB 32.99% github.com/rivo/tview.(*TextView).parseAhead 14 | 0 0% 34.02% -528.17kB 34.02% github.com/f1bonacc1/process-compose/src/app.(*Process).handleInfo 15 | 0 0% 34.02% -528.17kB 34.02% github.com/f1bonacc1/process-compose/src/app.(*Process).handleOutput 16 | 0 0% 34.02% -512.06kB 32.99% github.com/f1bonacc1/process-compose/src/cmd.startTui 17 | 0 0% 34.02% -528.17kB 34.02% github.com/f1bonacc1/process-compose/src/pclog.(*ProcessLogBuffer).Write 18 | 0 0% 34.02% -528.17kB 34.02% github.com/f1bonacc1/process-compose/src/tui.(*LogView).WriteString 19 | 0 0% 34.02% -512.06kB 32.99% github.com/f1bonacc1/process-compose/src/tui.(*pcView).updateLogs.(*Application).QueueUpdateDraw.func4 20 | 0 0% 34.02% -512.06kB 32.99% github.com/f1bonacc1/process-compose/src/tui.SetupTui 21 | -------------------------------------------------------------------------------- /issues/issue_111/process-compose-probes.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | log_level: debug 4 | log_length: 1000 5 | 6 | processes: 7 | procA: 8 | command: "exit 1" 9 | readiness_probe: 10 | exec: 11 | command: "pidof process-compose" 12 | initial_delay_seconds: 1 13 | period_seconds: 1 14 | timeout_seconds: 1 15 | success_threshold: 1 16 | failure_threshold: 20 17 | 18 | procB: 19 | command: echo "I shouldn't run" 20 | readiness_probe: 21 | exec: 22 | command: "pidof process-compose" 23 | initial_delay_seconds: 1 24 | period_seconds: 1 25 | timeout_seconds: 1 26 | success_threshold: 1 27 | failure_threshold: 20 28 | depends_on: 29 | procA: 30 | condition: process_healthy 31 | 32 | procC: 33 | command: echo "I shouldn't run" 34 | depends_on: 35 | procB: 36 | condition: process_healthy 37 | 38 | pc_log: 39 | command: "tail -f -n100 process-compose-${USER}.log" 40 | working_dir: "/tmp" 41 | namespace: debug 42 | 43 | -------------------------------------------------------------------------------- /issues/issue_111/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | log_level: debug 4 | log_length: 1000 5 | 6 | processes: 7 | procA: 8 | command: "exit 1" 9 | 10 | procB: 11 | command: echo "I shouldn't run" 12 | depends_on: 13 | procA: 14 | condition: process_completed_successfully 15 | 16 | procC: 17 | command: echo "I shouldn't run" 18 | depends_on: 19 | procB: 20 | condition: process_completed_successfully 21 | 22 | pc_log: 23 | command: "tail -f -n100 process-compose-${USER}.log" 24 | working_dir: "/tmp" 25 | namespace: debug 26 | 27 | -------------------------------------------------------------------------------- /issues/issue_120/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | environment: 4 | - "I_AM_GLOBAL_EV=5" 5 | vars: 6 | EV: 5 7 | 8 | processes: 9 | process1: 10 | description: This process will sleep for 2 seconds 11 | command: "echo {{ .EV }} > /tmp/output" 12 | -------------------------------------------------------------------------------- /issues/issue_156/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: info 3 | log_length: 300 4 | 5 | processes: 6 | block_folded: 7 | command: > 8 | echo 1 9 | && echo 2 10 | 11 | echo 3 12 | 13 | block_literal: 14 | command: | 15 | echo 4 16 | echo 5 17 | depends_on: 18 | block_folded: 19 | condition: process_completed 20 | 21 | flow_single: 22 | command: 'echo 6 23 | && echo 7 24 | 25 | echo 8' 26 | depends_on: 27 | block_literal: 28 | condition: process_completed 29 | 30 | flow_double: 31 | command: "echo 8 32 | && echo 9 33 | 34 | echo 10" 35 | depends_on: 36 | flow_single: 37 | condition: process_completed 38 | 39 | flow_plain: 40 | command: echo 11 41 | && echo 12 42 | 43 | echo 13 44 | depends_on: 45 | flow_double: 46 | condition: process_completed 47 | 48 | -------------------------------------------------------------------------------- /issues/issue_166/process-compose.yaml: -------------------------------------------------------------------------------- 1 | is_strict: true 2 | 3 | processes: 4 | pc_log: 5 | command: "tail -f -n100 process-compose-${USER}.log" 6 | working_dir: "/tmp" 7 | test: 8 | command: | 9 | echo "TOTO" 10 | sleep 40 11 | depends_on: 12 | runtrial: 13 | # condition: toto # this is accepted and doesn't trigger any error even though 14 | condition: process_completed_successfully 15 | 16 | # one shot task, hence why we disable it now 17 | runtrial: 18 | namespace: task 19 | disabled: true 20 | command: 21 | echo "TRIAL DONE" 22 | -------------------------------------------------------------------------------- /issues/issue_177/process-compose.override.yml: -------------------------------------------------------------------------------- 1 | processes: 2 | test: 3 | disabled: false 4 | command: echo bar 5 | pc_log: 6 | command: "tail -f -n100 process-compose-${USER}.log" 7 | working_dir: "/tmp" 8 | -------------------------------------------------------------------------------- /issues/issue_177/process-compose.yml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | test: 4 | disabled: true 5 | command: echo foo 6 | -------------------------------------------------------------------------------- /issues/issue_189/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | pc_log: 4 | command: "tail -f -n100 process-compose-${USER}.log" 5 | working_dir: "/tmp" 6 | date: 7 | command: while true; do date && sleep 1; done 8 | availability: 9 | restart: "always" 10 | readiness_probe: 11 | exec: 12 | command: "ps -ef | grep -v grep | grep kcalc" 13 | initial_delay_seconds: 5 14 | period_seconds: 2 15 | timeout_seconds: 1 16 | success_threshold: 1 17 | failure_threshold: 3 18 | waiter: 19 | command: echo "date is ready now" 20 | depends_on: 21 | date: 22 | condition: process_healthy 23 | -------------------------------------------------------------------------------- /issues/issue_191/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | noisy: 4 | command: while true; do date; done 5 | -------------------------------------------------------------------------------- /issues/issue_200/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: info 3 | log_length: 300 4 | 5 | processes: 6 | sleep1: 7 | command: "sleep 1 && echo SUCCESS && sleep 3" 8 | 9 | echo1: 10 | command: "echo 'Im healthy'" 11 | -------------------------------------------------------------------------------- /issues/issue_214/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: info 3 | log_length: 300 4 | 5 | processes: 6 | pc_log: 7 | command: "tail -f -n100 process-compose-${USER}.log" 8 | working_dir: "/tmp" 9 | shutdown: 10 | timeout_seconds: 5 11 | 12 | sigterm_resistant: 13 | command: "trap '' SIGTERM && sleep 60" 14 | shutdown: 15 | timeout_seconds: 5 16 | -------------------------------------------------------------------------------- /issues/issue_237/logger.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | cat << EOF > process-compose.yml 6 | version: "0.5" 7 | log_level: debug 8 | 9 | processes: 10 | sleep: 11 | command: "sleep 100" 12 | echo: 13 | command: | 14 | echo 1 15 | sleep .1 16 | echo 2 17 | EOF 18 | 19 | ../../bin/process-compose up -u pc.sock -L pc.log --tui=false >/dev/null 2>&1 & 20 | 21 | # make sure process-compose has started 22 | for i in {1..5}; do 23 | if ../../bin/process-compose process list -u pc.sock 2>/dev/null | grep sleep >/dev/null; then 24 | break; 25 | fi 26 | sleep .1 27 | done 28 | if [ "$i" == 5 ]; then 29 | echo "didn't start" 30 | exit 1 31 | fi 32 | 33 | sleep .2 34 | ../../bin/process-compose -u pc.sock process logs echo --tail 5 > echo.log 35 | if ! grep 2 echo.log; then 36 | echo "waiting longer" 37 | sleep 10 38 | ../../bin/process-compose -u pc.sock process logs echo --tail 5 39 | ../../bin/process-compose down -u pc.sock 40 | exit 1 41 | fi 42 | ../../bin/process-compose down -u pc.sock 43 | -------------------------------------------------------------------------------- /issues/issue_237/process-compose.yml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | 4 | processes: 5 | sleep: 6 | command: "sleep 100" 7 | echo: 8 | command: | 9 | echo 1 10 | sleep .1 11 | echo 2 12 | -------------------------------------------------------------------------------- /issues/issue_238/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | processes: 3 | elevated: 4 | command: echo "hi" 5 | is_elevated: true 6 | pc_log: 7 | command: "tail -f -n100 process-compose-${USER}.log" 8 | working_dir: "/tmp" 9 | date: 10 | command: while true; do date && sleep 1; done 11 | disabled: true 12 | -------------------------------------------------------------------------------- /issues/issue_239/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | 3 | processes: 4 | api: 5 | disabled: true # <-- The bug only occurs when this is set to `true` 6 | command: echo hello 7 | -------------------------------------------------------------------------------- /issues/issue_258/devbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.0/.schema/devbox.schema.json", 3 | "packages": { 4 | "postgresql":"latest", 5 | "glibcLocales": { 6 | "version": "latest", 7 | "platforms": ["x86_64-linux", "aarch64-linux"] 8 | } 9 | }, 10 | "shell": { 11 | "init_hook": [ 12 | "echo 'Welcome to devbox!' > /dev/null" 13 | ], 14 | "scripts": { 15 | "test": [ 16 | "echo \"Error: no test specified\" && exit 1" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /issues/issue_258/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | log_length: 3000 4 | is_strict: true 5 | disable_env_expansion: true 6 | 7 | processes: 8 | postgresql: 9 | command: "exec pg_ctl start -o \"-k /home/eugene/projects/go/process-compose/issues/issue_258/.devbox/virtenv/postgresql\" && wait" 10 | is_daemon: true 11 | launch_timeout_seconds: 2 12 | shutdown: 13 | command: "pg_ctl stop -m fast" 14 | availability: 15 | restart: "always" 16 | readiness_probe: 17 | exec: 18 | command: "pg_isready" 19 | pc_log: 20 | command: "tail -f -n100 process-compose-${USER}.log" 21 | working_dir: "/tmp" 22 | environment: 23 | -------------------------------------------------------------------------------- /issues/issue_274/process-compose.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": { 3 | "bundle-install": { "command": "sleep 10", "namespace": "default" }, 4 | "configure": { "command": "diff a b", "namespace": "default" }, 5 | "mysql": { 6 | "availability": { "max_restarts": 5, "restart": "on_failure" }, 7 | "command": "sleep 100", 8 | "namespace": "mysql.mysql", 9 | "readiness_probe": { 10 | "exec": { 11 | "command": "echo mysql" 12 | }, 13 | "failure_threshold": 5, 14 | "initial_delay_seconds": 2, 15 | "period_seconds": 10, 16 | "success_threshold": 1, 17 | "timeout_seconds": 4 18 | } 19 | }, 20 | "mysql-configure": { 21 | "command": "sleep 3", 22 | "depends_on": { "mysql": { "condition": "process_healthy" } }, 23 | "namespace": "mysql.mysql" 24 | }, 25 | "prepare-db": { 26 | "command": "echo PREPARE DB", 27 | "depends_on": { 28 | "bundle-install": { "condition": "process_completed_successfully" }, 29 | "configure": { "condition": "process_completed_successfully" }, 30 | "mysql-configure": { "condition": "process_completed_successfully" } 31 | }, 32 | "namespace": "default" 33 | }, 34 | "web": { 35 | "command": "sleep 100", 36 | "depends_on": { 37 | "bundle-install": { "condition": "process_completed_successfully" }, 38 | "configure": { "condition": "process_completed_successfully" }, 39 | "prepare-db": { "condition": "process_completed_successfully" } 40 | }, 41 | "namespace": "default" 42 | } 43 | }, 44 | "shell": { "shell_argument": "-c", "shell_command": "/bin/bash" } 45 | } 46 | -------------------------------------------------------------------------------- /issues/issue_278/db/db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "db started" 3 | -------------------------------------------------------------------------------- /issues/issue_278/db/process-compose.yaml: -------------------------------------------------------------------------------- 1 | processes: 2 | # starts DB process 3 | db: 4 | command: ./db.sh 5 | -------------------------------------------------------------------------------- /issues/issue_278/server/process-compose.yaml: -------------------------------------------------------------------------------- 1 | extends: ../db/process-compose.yaml 2 | 3 | processes: 4 | # starts server process, which depends on `db` process of the `db` package: 5 | server: 6 | command: ./server.sh 7 | depends_on: 8 | db: 9 | condition: process_completed_successfully 10 | -------------------------------------------------------------------------------- /issues/issue_278/server/server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "server started" 3 | -------------------------------------------------------------------------------- /issues/issue_280/process-compose.yaml: -------------------------------------------------------------------------------- 1 | processes: 2 | process0: 3 | command: "echo process0" 4 | depends_on: 5 | process2: 6 | condition: process_started 7 | process1: 8 | command: "echo process1" 9 | depends_on: 10 | process2: 11 | condition: process_started 12 | process2: 13 | command: "echo process2" 14 | depends_on: 15 | process3: 16 | condition: process_completed_successfully 17 | process3: 18 | command: "sleep 9" 19 | -------------------------------------------------------------------------------- /issues/issue_316/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | is_strict: true 3 | 4 | env_cmds: 5 | HI: "echo Hello" 6 | 7 | 8 | processes: 9 | hello: 10 | command: sleep 5 11 | liveness_probe: 12 | exec: 13 | command: echo $${HI} there > test.txt 14 | -------------------------------------------------------------------------------- /issues/issue_49/bad_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Define function to spawn a sub-process 4 | spawn_subprocess() { 5 | sleep 3600 & 6 | echo "Spawned subprocess with PID $!" 7 | } 8 | 9 | # Continuously spawn sub-processes 10 | while true; do 11 | spawn_subprocess 12 | sleep 1 13 | done 14 | -------------------------------------------------------------------------------- /issues/issue_49/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | log_length: 300 4 | 5 | processes: 6 | bad_script: 7 | command: "./bad_script.sh" 8 | 9 | _pc_log: 10 | command: "tail -f -n100 process-compose-${USER}.log" 11 | working_dir: "/tmp" 12 | 13 | -------------------------------------------------------------------------------- /issues/issue_57/long_log.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import datetime 4 | 5 | cntr=0 6 | line = "=" 7 | while True: 8 | print("long line number {0}: {1}".format(cntr, line)) 9 | cntr += 1 10 | line += line 11 | 12 | -------------------------------------------------------------------------------- /issues/issue_57/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | 4 | processes: 5 | bad_script: 6 | command: "./long_log.py" 7 | 8 | _pc_log: 9 | command: "tail -f -n100 process-compose-${USER}.log" 10 | working_dir: "/tmp" 11 | -------------------------------------------------------------------------------- /issues/issue_57/too_chatty.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import datetime 4 | 5 | start = datetime.datetime.now() 6 | cntr=0 7 | duration="" 8 | while True: 9 | print("I am long line number {0} ====================================== Duration per 100k: {1}".format(cntr, duration)) 10 | cntr += 1 11 | if cntr % 100000 == 0: 12 | duration = datetime.datetime.now() - start 13 | start = datetime.datetime.now() 14 | 15 | -------------------------------------------------------------------------------- /issues/issue_72/clientA/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/issues/issue_72/clientA/.gitkeep -------------------------------------------------------------------------------- /issues/issue_72/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | log_length: 300 4 | 5 | processes: 6 | clientA: 7 | working_dir: "clientA" 8 | is_daemon: true 9 | command: "sleep 10 && touch ready" 10 | shutdown: 11 | command: "rm ready" 12 | readiness_probe: 13 | exec: 14 | command: "echo $(pwd) > ready-check && test -f ready" 15 | clientB: 16 | command: "echo all done!" 17 | depends_on: 18 | clientA: 19 | condition: process_healthy 20 | 21 | _pc_log: 22 | command: "tail -f -n100 process-compose-${USER}.log" 23 | working_dir: "/tmp" 24 | 25 | -------------------------------------------------------------------------------- /issues/issue_84/process-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "0.5" 2 | log_level: debug 3 | log_length: 300 4 | 5 | processes: 6 | clientA: 7 | command: "sleep 5 && touch ready" 8 | 9 | 10 | -------------------------------------------------------------------------------- /process-compose-win.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/F1bonacc1/process-compose/main/schemas/process-compose-schema.json 2 | 3 | version: "0.5" 4 | log_level: debug 5 | processes: 6 | process0: 7 | command: "ls ddd" 8 | 9 | process1: 10 | command: "powershell.exe ./test_loop.ps1 ${PROC4}" 11 | availability: 12 | restart: "on_failure" 13 | backoff_seconds: 2 14 | depends_on: 15 | process2: 16 | condition: process_completed_successfully 17 | process3: 18 | condition: process_completed 19 | # process4: 20 | # condition: process_completed_successfully 21 | environment: 22 | - 'EXIT_CODE=0' 23 | 24 | process2: 25 | command: "powershell.exe ./test_loop.ps1 process2" 26 | log_location: ./pc.proc2.log 27 | availability: 28 | restart: "on_failure" 29 | # depends_on: 30 | # process3: 31 | # condition: process_completed_successfully 32 | environment: 33 | - 'ABC=2221' 34 | - 'PRINT_ERR=111' 35 | - 'EXIT_CODE=0' 36 | 37 | process3: 38 | command: "powershell.exe ./test_loop.ps1 process3" 39 | availability: 40 | restart: "on_failure" 41 | backoff_seconds: 2 42 | depends_on: 43 | process4: 44 | condition: process_completed_successfully 45 | 46 | process4: 47 | command: "powershell.exe ./test_loop.ps1 process4" 48 | # availability: 49 | # restart: on_failure 50 | environment: 51 | - 'ABC=2221' 52 | - 'EXIT_CODE=1' 53 | 54 | kcalc: 55 | command: "calc" 56 | disabled: true 57 | 58 | environment: 59 | - 'ABC=222' 60 | log_location: ./pc.log 61 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | }: 4 | pkgs.mkShell { 5 | packages = with pkgs; [ 6 | go 7 | gopls 8 | gotools 9 | gnumake 10 | gnused 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /src/admitter/admitter.go: -------------------------------------------------------------------------------- 1 | package admitter 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | type Admitter interface { 6 | Admit(config *types.ProcessConfig) bool 7 | } 8 | -------------------------------------------------------------------------------- /src/admitter/disabled.go: -------------------------------------------------------------------------------- 1 | package admitter 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | type DisabledProcAdmitter struct { 6 | } 7 | 8 | func (d *DisabledProcAdmitter) Admit(proc *types.ProcessConfig) bool { 9 | return !proc.Disabled 10 | } 11 | -------------------------------------------------------------------------------- /src/admitter/namespace.go: -------------------------------------------------------------------------------- 1 | package admitter 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | type NamespaceAdmitter struct { 6 | EnabledNamespaces []string 7 | } 8 | 9 | func (n *NamespaceAdmitter) Admit(proc *types.ProcessConfig) bool { 10 | if len(n.EnabledNamespaces) == 0 { 11 | return true 12 | } 13 | for _, ns := range n.EnabledNamespaces { 14 | if ns == proc.Namespace { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /src/admitter/namespace_test.go: -------------------------------------------------------------------------------- 1 | package admitter 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/types" 5 | "testing" 6 | ) 7 | 8 | func TestNamespaceAdmitter_Admit(t *testing.T) { 9 | type fields struct { 10 | EnabledNamespaces []string 11 | } 12 | type args struct { 13 | proc *types.ProcessConfig 14 | } 15 | tests := []struct { 16 | name string 17 | fields fields 18 | args args 19 | want bool 20 | }{ 21 | { 22 | name: "no namespace", 23 | fields: fields{ 24 | EnabledNamespaces: []string{}, 25 | }, 26 | args: args{ 27 | proc: &types.ProcessConfig{ 28 | Namespace: "", 29 | }, 30 | }, 31 | want: true, 32 | }, 33 | { 34 | name: "nil namespace", 35 | fields: fields{ 36 | EnabledNamespaces: nil, 37 | }, 38 | args: args{ 39 | proc: &types.ProcessConfig{ 40 | Namespace: "", 41 | }, 42 | }, 43 | want: true, 44 | }, 45 | { 46 | name: "mismatched namespace", 47 | fields: fields{ 48 | EnabledNamespaces: []string{"test"}, 49 | }, 50 | args: args{ 51 | proc: &types.ProcessConfig{ 52 | Namespace: "not-test", 53 | }, 54 | }, 55 | want: false, 56 | }, 57 | { 58 | name: "matched namespace", 59 | fields: fields{ 60 | EnabledNamespaces: []string{"test"}, 61 | }, 62 | args: args{ 63 | proc: &types.ProcessConfig{ 64 | Namespace: "test", 65 | }, 66 | }, 67 | want: true, 68 | }, 69 | { 70 | name: "matched namespaces", 71 | fields: fields{ 72 | EnabledNamespaces: []string{"not-test", "test"}, 73 | }, 74 | args: args{ 75 | proc: &types.ProcessConfig{ 76 | Namespace: "test", 77 | }, 78 | }, 79 | want: true, 80 | }, 81 | } 82 | for _, tt := range tests { 83 | t.Run(tt.name, func(t *testing.T) { 84 | n := &NamespaceAdmitter{ 85 | EnabledNamespaces: tt.fields.EnabledNamespaces, 86 | } 87 | if got := n.Admit(tt.args.proc); got != tt.want { 88 | t.Errorf("Admit() = %v, want %v", got, tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/api/routes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | _ "github.com/f1bonacc1/process-compose/src/docs" 5 | "github.com/gin-gonic/gin" 6 | swaggerFiles "github.com/swaggo/files" 7 | ginSwagger "github.com/swaggo/gin-swagger" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // InitRoutes initialize routing information 13 | func InitRoutes(useLogger bool, handler *PcApi) *gin.Engine { 14 | r := gin.New() 15 | if useLogger { 16 | r.Use(gin.Logger()) 17 | } 18 | r.Use(gin.Recovery()) 19 | 20 | r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 21 | r.GET("/", func(c *gin.Context) { 22 | location := url.URL{Path: "/swagger/index.html"} 23 | c.Redirect(http.StatusFound, location.RequestURI()) 24 | }) 25 | 26 | r.GET("/live", handler.IsAlive) 27 | r.GET("/hostname", handler.GetHostName) 28 | r.GET("/processes", handler.GetProcesses) 29 | r.GET("/process/:name", handler.GetProcess) 30 | r.GET("/process/info/:name", handler.GetProcessInfo) 31 | r.POST("/process", handler.UpdateProcess) 32 | r.GET("/process/ports/:name", handler.GetProcessPorts) 33 | r.GET("/process/logs/:name/:endOffset/:limit", handler.GetProcessLogs) 34 | r.DELETE("/process/logs/:name", handler.TruncateProcessLogs) 35 | r.PATCH("/process/stop/:name", handler.StopProcess) 36 | r.PATCH("/processes/stop", handler.StopProcesses) 37 | r.POST("/process/start/:name", handler.StartProcess) 38 | r.POST("/process/restart/:name", handler.RestartProcess) 39 | r.POST("/project/stop", handler.ShutDownProject) 40 | r.POST("/project", handler.UpdateProject) 41 | r.POST("/project/configuration", handler.ReloadProject) 42 | r.GET("/project/state", handler.GetProjectState) 43 | r.PATCH("/process/scale/:name/:scale", handler.ScaleProcess) 44 | 45 | //websocket 46 | r.GET("/process/logs/ws", handler.HandleLogsStream) 47 | 48 | return r 49 | } 50 | -------------------------------------------------------------------------------- /src/api/server.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/app" 6 | "github.com/gin-gonic/gin" 7 | "github.com/rs/zerolog/log" 8 | "net" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | const EnvDebugMode = "PC_DEBUG_MODE" 14 | 15 | func StartHttpServerWithUnixSocket(useLogger bool, unixSocket string, project app.IProject) (*http.Server, error) { 16 | router := getRouter(useLogger, project) 17 | log.Info().Msgf("start UDS http server listening %s", unixSocket) 18 | 19 | // Check if the unix socket is already in use 20 | // If it exists but we can't connect, remove it 21 | _, err := net.Dial("unix", unixSocket) 22 | if err == nil { 23 | log.Fatal().Msgf("unix socket %s is already in use", unixSocket) 24 | } 25 | os.Remove(unixSocket) 26 | 27 | server := &http.Server{ 28 | Handler: router.Handler(), 29 | } 30 | 31 | listener, err := net.Listen("unix", unixSocket) 32 | if err != nil { 33 | return server, err 34 | } 35 | 36 | go func() { 37 | defer listener.Close() 38 | defer os.Remove(unixSocket) 39 | 40 | if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { 41 | log.Fatal().Err(err).Msgf("start UDS http server on %s failed", unixSocket) 42 | } 43 | }() 44 | 45 | return server, nil 46 | } 47 | 48 | func StartHttpServerWithTCP(useLogger bool, port int, project app.IProject) (*http.Server, error) { 49 | router := getRouter(useLogger, project) 50 | endPoint := fmt.Sprintf(":%d", port) 51 | log.Info().Msgf("start http server listening %s", endPoint) 52 | 53 | server := &http.Server{ 54 | Addr: endPoint, 55 | Handler: router.Handler(), 56 | } 57 | 58 | go func() { 59 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 60 | log.Fatal().Err(err).Msgf("start http server on %s failed", endPoint) 61 | } 62 | }() 63 | 64 | return server, nil 65 | } 66 | 67 | func getRouter(useLogger bool, project app.IProject) *gin.Engine { 68 | if os.Getenv(EnvDebugMode) == "" { 69 | gin.SetMode(gin.ReleaseMode) 70 | useLogger = false 71 | } 72 | return InitRoutes(useLogger, NewPcApi(project)) 73 | } 74 | -------------------------------------------------------------------------------- /src/api/types.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type LogMessage struct { 4 | Message string `json:"message"` 5 | ProcessName string `json:"process_name"` 6 | } 7 | -------------------------------------------------------------------------------- /src/app/daemon.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | func (p *Process) waitForDaemonCompletion() { 6 | if !p.isDaemonLaunched() { 7 | return 8 | } 9 | 10 | loop: 11 | for { 12 | status := <-p.procStateChan 13 | switch status { 14 | case types.ProcessStateCompleted: 15 | break loop 16 | } 17 | } 18 | 19 | } 20 | 21 | func (p *Process) notifyDaemonStopped() { 22 | if p.procConf.IsDaemon { 23 | p.procStateChan <- types.ProcessStateCompleted 24 | } 25 | } 26 | 27 | func (p *Process) isDaemonLaunched() bool { 28 | return p.procConf.IsDaemon && p.procState.ExitCode == 0 29 | } 30 | -------------------------------------------------------------------------------- /src/app/pc_string.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | func isStringDefined(str string) bool { 10 | return len(strings.TrimSpace(str)) > 0 11 | } 12 | 13 | // HumanDuration returns a succinct representation of the provided duration 14 | // with limited precision for consumption by humans. It provides ~2-3 significant 15 | // figures of duration. 16 | func HumanDuration(d time.Duration) string { 17 | // Allow deviation no more than 2 seconds(excluded) to tolerate machine time 18 | // inconsistence, it can be considered as almost now. 19 | if seconds := int(d.Seconds()); seconds < -1 { 20 | return "" 21 | } else if seconds < 0 { 22 | return "0s" 23 | } else if seconds < 60*2 { 24 | return fmt.Sprintf("%ds", seconds) 25 | } 26 | minutes := int(d / time.Minute) 27 | if minutes < 10 { 28 | s := int(d/time.Second) % 60 29 | if s == 0 { 30 | return fmt.Sprintf("%dm", minutes) 31 | } 32 | return fmt.Sprintf("%dm%ds", minutes, s) 33 | } else if minutes < 60*3 { 34 | return fmt.Sprintf("%dm", minutes) 35 | } 36 | hours := int(d / time.Hour) 37 | if hours < 8 { 38 | m := int(d/time.Minute) % 60 39 | if m == 0 { 40 | return fmt.Sprintf("%dh", hours) 41 | } 42 | return fmt.Sprintf("%dh%dm", hours, m) 43 | } else if hours < 48 { 44 | return fmt.Sprintf("%dh", hours) 45 | } else if hours < 24*8 { 46 | h := hours % 24 47 | if h == 0 { 48 | return fmt.Sprintf("%dd", hours/24) 49 | } 50 | return fmt.Sprintf("%dd%dh", hours/24, h) 51 | } else if hours < 24*365*2 { 52 | return fmt.Sprintf("%dd", hours/24) 53 | } else if hours < 24*365*8 { 54 | dy := hours / 24 % 365 55 | if dy == 0 { 56 | return fmt.Sprintf("%dy", hours/24/365) 57 | } 58 | return fmt.Sprintf("%dy%dd", hours/24/365, dy) 59 | } 60 | return fmt.Sprintf("%dy", hours/24/365) 61 | } 62 | -------------------------------------------------------------------------------- /src/app/proc_opts.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/command" 5 | "github.com/f1bonacc1/process-compose/src/pclog" 6 | "github.com/f1bonacc1/process-compose/src/types" 7 | "time" 8 | ) 9 | 10 | type ProcOpts func(proc *Process) 11 | 12 | func withTuiOn(isTuiEnabled bool) ProcOpts { 13 | return func(proc *Process) { 14 | proc.isTuiEnabled = isTuiEnabled 15 | } 16 | } 17 | 18 | func withGlobalEnv(globalEnv []string) ProcOpts { 19 | return func(proc *Process) { 20 | proc.globalEnv = globalEnv 21 | } 22 | } 23 | 24 | func withDotEnv(dotEnvVars map[string]string) ProcOpts { 25 | return func(proc *Process) { 26 | proc.dotEnvVars = dotEnvVars 27 | } 28 | } 29 | 30 | func withLogger(logger pclog.PcLogger) ProcOpts { 31 | return func(proc *Process) { 32 | proc.logger = logger 33 | } 34 | } 35 | 36 | func withProcConf(procConf *types.ProcessConfig) ProcOpts { 37 | return func(proc *Process) { 38 | proc.procConf = procConf 39 | } 40 | } 41 | 42 | func withProcState(procState *types.ProcessState) ProcOpts { 43 | return func(proc *Process) { 44 | proc.procState = procState 45 | } 46 | } 47 | 48 | func withProcLog(procLog *pclog.ProcessLogBuffer) ProcOpts { 49 | return func(proc *Process) { 50 | proc.logBuffer = procLog 51 | } 52 | } 53 | 54 | func withShellConfig(shellConfig command.ShellConfig) ProcOpts { 55 | return func(proc *Process) { 56 | proc.shellConfig = shellConfig 57 | } 58 | } 59 | 60 | func withPrintLogs(printLogs bool) ProcOpts { 61 | return func(proc *Process) { 62 | proc.printLogs = printLogs 63 | } 64 | } 65 | 66 | func withIsMain(isMain bool) ProcOpts { 67 | return func(proc *Process) { 68 | proc.isMain = isMain 69 | } 70 | } 71 | 72 | func withExtraArgs(extraArgs []string) ProcOpts { 73 | return func(proc *Process) { 74 | proc.extraArgs = extraArgs 75 | } 76 | } 77 | 78 | func withLogsTruncate(truncateLogs bool) ProcOpts { 79 | return func(proc *Process) { 80 | proc.truncateLogs = truncateLogs 81 | } 82 | } 83 | 84 | func withRefRate(refRate time.Duration) ProcOpts { 85 | return func(proc *Process) { 86 | proc.refRate = refRate 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/project_interface.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/pclog" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | ) 7 | 8 | // IProject holds all the functions from the project struct that are being consumed by the tui package 9 | type IProject interface { 10 | ShutDownProject() error 11 | IsRemote() bool 12 | ErrorForSecs() int 13 | GetHostName() (string, error) 14 | GetProjectState(checkMem bool) (*types.ProjectState, error) 15 | 16 | GetLogLength() int 17 | GetLogsAndSubscribe(name string, observer pclog.LogObserver) error 18 | UnSubscribeLogger(name string, observer pclog.LogObserver) error 19 | GetProcessLog(name string, offsetFromEnd, limit int) ([]string, error) 20 | 21 | GetLexicographicProcessNames() ([]string, error) 22 | GetProcessInfo(name string) (*types.ProcessConfig, error) 23 | GetProcessState(name string) (*types.ProcessState, error) 24 | GetProcessesState() (*types.ProcessesState, error) 25 | StopProcess(name string) error 26 | StopProcesses(names []string) (map[string]string, error) 27 | StartProcess(name string) error 28 | RestartProcess(name string) error 29 | ScaleProcess(name string, scale int) error 30 | GetProcessPorts(name string) (*types.ProcessPorts, error) 31 | SetProcessPassword(name string, password string) error 32 | UpdateProject(project *types.Project) (map[string]string, error) 33 | UpdateProcess(updated *types.ProcessConfig) error 34 | ReloadProject() (map[string]string, error) 35 | TruncateProcessLogs(name string) error 36 | } 37 | -------------------------------------------------------------------------------- /src/app/project_opts.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/types" 5 | "time" 6 | ) 7 | 8 | type ProjectOpts struct { 9 | project *types.Project 10 | processesToRun []string 11 | noDeps bool 12 | mainProcess string 13 | mainProcessArgs []string 14 | isTuiOn bool 15 | isOrderedShutDown bool 16 | disableDotenv bool 17 | truncateLogs bool 18 | refRate time.Duration 19 | } 20 | 21 | func (p *ProjectOpts) WithProject(project *types.Project) *ProjectOpts { 22 | p.project = project 23 | return p 24 | } 25 | 26 | func (p *ProjectOpts) WithProcessesToRun(processesToRun []string) *ProjectOpts { 27 | p.processesToRun = processesToRun 28 | return p 29 | } 30 | func (p *ProjectOpts) WithNoDeps(noDeps bool) *ProjectOpts { 31 | p.noDeps = noDeps 32 | return p 33 | } 34 | 35 | func (p *ProjectOpts) WithMainProcess(mainProcess string) *ProjectOpts { 36 | p.mainProcess = mainProcess 37 | return p 38 | } 39 | 40 | func (p *ProjectOpts) WithMainProcessArgs(mainProcessArgs []string) *ProjectOpts { 41 | p.mainProcessArgs = mainProcessArgs 42 | return p 43 | } 44 | 45 | func (p *ProjectOpts) WithIsTuiOn(isTuiOn bool) *ProjectOpts { 46 | p.isTuiOn = isTuiOn 47 | return p 48 | } 49 | 50 | func (p *ProjectOpts) WithOrderedShutDown(isOrderedShutDown bool) *ProjectOpts { 51 | p.isOrderedShutDown = isOrderedShutDown 52 | return p 53 | } 54 | 55 | func (p *ProjectOpts) WithDotEnvDisabled(disabled bool) *ProjectOpts { 56 | p.disableDotenv = disabled 57 | return p 58 | } 59 | 60 | func (p *ProjectOpts) WithLogTruncate(truncateLogs bool) *ProjectOpts { 61 | p.truncateLogs = truncateLogs 62 | return p 63 | } 64 | 65 | func (p *ProjectOpts) WithSlowRefRate(refRate time.Duration) *ProjectOpts { 66 | p.refRate = refRate 67 | return p 68 | } 69 | -------------------------------------------------------------------------------- /src/app/table.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | ) 7 | 8 | // Print a list of process states in a table. 9 | func PrintStatesAsTable(states []types.ProcessState) { 10 | 11 | // Create a table 12 | table := []string{"PID", "NAME", "NAMESPACE", "STATUS", "AGE", "HEALTH", "RESTARTS", "EXITCODE"} 13 | tableColWidth := make([]int, len(table)) 14 | 15 | for _, state := range states { 16 | if len(fmt.Sprintf("%d", state.Pid)) > tableColWidth[0] { 17 | tableColWidth[0] = len(fmt.Sprintf("%d", state.Pid)) 18 | } 19 | if len(state.Name) > tableColWidth[1] { 20 | tableColWidth[1] = len(state.Name) 21 | } 22 | if len(state.Namespace) > tableColWidth[2] { 23 | tableColWidth[2] = len(state.Namespace) 24 | } 25 | if len(state.Status) > tableColWidth[3] { 26 | tableColWidth[3] = len(state.Status) 27 | } 28 | if len(state.SystemTime) > tableColWidth[4] { 29 | tableColWidth[4] = len(state.SystemTime) 30 | } 31 | if len(state.Health) > tableColWidth[5] { 32 | tableColWidth[5] = len(state.Health) 33 | } 34 | if len(fmt.Sprintf("%d", state.Restarts)) > tableColWidth[6] { 35 | tableColWidth[6] = len(fmt.Sprintf("%d", state.Restarts)) 36 | } 37 | if len(fmt.Sprintf("%d", state.ExitCode)) > tableColWidth[7] { 38 | tableColWidth[7] = len(fmt.Sprintf("%d", state.ExitCode)) 39 | } 40 | } 41 | for i, col := range table { 42 | if len(col) > tableColWidth[i] { 43 | tableColWidth[i] = len(col) 44 | } 45 | } 46 | for i, col := range table { 47 | fmt.Printf("%-*s ", tableColWidth[i], col) 48 | } 49 | fmt.Println() 50 | for _, state := range states { 51 | fmt.Printf("%-*d ", tableColWidth[0], state.Pid) 52 | fmt.Printf("%-*s ", tableColWidth[1], state.Name) 53 | fmt.Printf("%-*s ", tableColWidth[2], state.Namespace) 54 | fmt.Printf("%-*s ", tableColWidth[3], state.Status) 55 | fmt.Printf("%-*s ", tableColWidth[4], state.SystemTime) 56 | fmt.Printf("%-*s ", tableColWidth[5], state.Health) 57 | fmt.Printf("%-*d ", tableColWidth[6], state.Restarts) 58 | fmt.Printf("%-*d ", tableColWidth[7], state.ExitCode) 59 | fmt.Println() 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/client/common.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type pcError struct { 4 | Error string `json:"error"` 5 | } 6 | -------------------------------------------------------------------------------- /src/client/restart.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/rs/zerolog/log" 8 | "net/http" 9 | ) 10 | 11 | func (p *PcClient) restartProcess(name string) error { 12 | url := fmt.Sprintf("http://%s/process/restart/%s", p.address, name) 13 | resp, err := p.client.Post(url, "application/json", nil) 14 | if err != nil { 15 | return err 16 | } 17 | if resp.StatusCode == http.StatusOK { 18 | return nil 19 | } 20 | defer resp.Body.Close() 21 | var respErr pcError 22 | if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 23 | log.Error().Msgf("failed to decode restart process %s response: %v", name, err) 24 | return err 25 | } 26 | return errors.New(respErr.Error) 27 | } 28 | -------------------------------------------------------------------------------- /src/client/scale_process.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/rs/zerolog/log" 8 | "net/http" 9 | ) 10 | 11 | func (p *PcClient) scaleProcess(name string, scale int) error { 12 | url := fmt.Sprintf("http://%s/process/scale/%s/%d", p.address, name, scale) 13 | req, err := http.NewRequest(http.MethodPatch, url, nil) 14 | if err != nil { 15 | return err 16 | } 17 | resp, err := p.client.Do(req) 18 | if err != nil { 19 | return err 20 | } 21 | if resp.StatusCode == http.StatusOK { 22 | return nil 23 | } 24 | defer resp.Body.Close() 25 | var respErr pcError 26 | if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 27 | log.Error().Msgf("failed to decode scale process %s response: %v", name, err) 28 | return err 29 | } 30 | return errors.New(respErr.Error) 31 | } 32 | -------------------------------------------------------------------------------- /src/client/start.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/rs/zerolog/log" 8 | "net/http" 9 | ) 10 | 11 | func (p *PcClient) startProcess(name string) error { 12 | url := fmt.Sprintf("http://%s/process/start/%s", p.address, name) 13 | resp, err := p.client.Post(url, "application/json", nil) 14 | if err != nil { 15 | return err 16 | } 17 | if resp.StatusCode == http.StatusOK { 18 | return nil 19 | } 20 | defer resp.Body.Close() 21 | var respErr pcError 22 | if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 23 | log.Error().Msgf("failed to decode start process %s response: %v", name, err) 24 | return err 25 | } 26 | return errors.New(respErr.Error) 27 | } 28 | -------------------------------------------------------------------------------- /src/client/status.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/rs/zerolog/log" 7 | "net/http" 8 | ) 9 | 10 | func (p *PcClient) isAlive() error { 11 | url := fmt.Sprintf("http://%s/live", p.address) 12 | resp, err := p.client.Get(url) 13 | if err != nil { 14 | return err 15 | } 16 | defer resp.Body.Close() 17 | if resp.StatusCode != http.StatusOK { 18 | return fmt.Errorf("unexpected status %s", resp.Status) 19 | } 20 | return nil 21 | } 22 | 23 | func (p *PcClient) getHostName() (string, error) { 24 | url := fmt.Sprintf("http://%s/hostname", p.address) 25 | resp, err := p.client.Get(url) 26 | if err != nil { 27 | return "", err 28 | } 29 | defer resp.Body.Close() 30 | if resp.StatusCode != http.StatusOK { 31 | return "", fmt.Errorf("unexpected status %s", resp.Status) 32 | } 33 | 34 | nameMap := map[string]string{} 35 | //Decode the data 36 | if err = json.NewDecoder(resp.Body).Decode(&nameMap); err != nil { 37 | log.Err(err).Send() 38 | return "", err 39 | } 40 | return nameMap["name"], nil 41 | } 42 | -------------------------------------------------------------------------------- /src/client/stop.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/rs/zerolog/log" 9 | "net/http" 10 | ) 11 | 12 | func (p *PcClient) stopProcess(name string) error { 13 | url := fmt.Sprintf("http://%s/process/stop/%s", p.address, name) 14 | req, err := http.NewRequest(http.MethodPatch, url, nil) 15 | if err != nil { 16 | return err 17 | } 18 | resp, err := p.client.Do(req) 19 | if err != nil { 20 | return err 21 | } 22 | defer resp.Body.Close() 23 | if resp.StatusCode == http.StatusOK { 24 | return nil 25 | } 26 | 27 | var respErr pcError 28 | if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 29 | log.Error().Msgf("failed to decode stop process %s response: %v", name, err) 30 | return err 31 | } 32 | return errors.New(respErr.Error) 33 | } 34 | 35 | func (p *PcClient) stopProcesses(names []string) (map[string]string, error) { 36 | url := fmt.Sprintf("http://%s/processes/stop", p.address) 37 | jsonPayload, err := json.Marshal(names) 38 | if err != nil { 39 | log.Err(err).Msgf("failed to marshal names: %v", names) 40 | return nil, err 41 | } 42 | req, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonPayload)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | resp, err := p.client.Do(req) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer resp.Body.Close() 51 | if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusMultiStatus { 52 | stopped := map[string]string{} 53 | if err = json.NewDecoder(resp.Body).Decode(&stopped); err != nil { 54 | log.Err(err).Msgf("failed to decode stop processes %v", names) 55 | return stopped, err 56 | } 57 | log.Info().Msgf("stopped: %v", stopped) 58 | 59 | return stopped, nil 60 | } 61 | var respErr pcError 62 | if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { 63 | log.Err(err).Msgf("failed to decode err stop processes %v", names) 64 | return nil, err 65 | } 66 | return nil, errors.New(respErr.Error) 67 | } 68 | -------------------------------------------------------------------------------- /src/cmd/0-init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/config" 6 | "github.com/f1bonacc1/process-compose/src/tui" 7 | "github.com/joho/godotenv" 8 | "github.com/spf13/pflag" 9 | "strings" 10 | ) 11 | 12 | var pcFlags *config.Flags 13 | var commonFlags *pflag.FlagSet 14 | 15 | const ( 16 | flagTheme = "theme" 17 | flagReverse = "reverse" 18 | flagSort = "sort" 19 | ) 20 | 21 | func init() { 22 | _ = godotenv.Load(".pc_env") 23 | pcFlags = config.NewFlags() 24 | commonFlags = pflag.NewFlagSet("", pflag.ContinueOnError) 25 | commonFlags.BoolVarP(pcFlags.IsReverseSort, flagReverse, "R", *pcFlags.IsReverseSort, "sort in reverse order") 26 | commonFlags.StringVarP( 27 | pcFlags.SortColumn, 28 | flagSort, 29 | "S", 30 | *pcFlags.SortColumn, 31 | fmt.Sprintf("sort column name. legal values (case insensitive): [%s]", strings.Join(tui.ColumnNames(), ", ")), 32 | ) 33 | commonFlags.StringVar(pcFlags.PcTheme, flagTheme, *pcFlags.PcTheme, "select process compose theme") 34 | } 35 | -------------------------------------------------------------------------------- /src/cmd/attach.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // attachCmd represents the attach command 8 | var attachCmd = &cobra.Command{ 9 | Use: "attach", 10 | Short: "Attach the Process Compose TUI Remotely to a Running Process Compose Server", 11 | Run: func(cmd *cobra.Command, args []string) { 12 | startTui(getClient(), false) 13 | }, 14 | } 15 | 16 | func init() { 17 | rootCmd.AddCommand(attachCmd) 18 | attachCmd.Flags().VarP(refreshRateFlag{pcFlags.RefreshRate}, "ref-rate", "r", "TUI refresh rate in seconds or as a Go duration string (e.g. 1s)") 19 | attachCmd.Flags().StringVarP(pcFlags.Address, "address", "a", *pcFlags.Address, "address of the target process compose server") 20 | attachCmd.Flags().IntVarP(pcFlags.LogLength, "log-length", "l", *pcFlags.LogLength, "log length to display in TUI") 21 | attachCmd.Flags().AddFlag(commonFlags.Lookup(flagReverse)) 22 | attachCmd.Flags().AddFlag(commonFlags.Lookup(flagSort)) 23 | attachCmd.Flags().AddFlag(commonFlags.Lookup(flagTheme)) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/cmd/docs.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "github.com/spf13/cobra/doc" 7 | 8 | "github.com/rs/zerolog/log" 9 | "github.com/spf13/cobra" 10 | "github.com/f1bonacc1/process-compose/src/types" 11 | "github.com/invopop/jsonschema" 12 | "github.com/stoewer/go-strcase" 13 | ) 14 | 15 | // docsCmd represents the docs command 16 | var docsCmd = &cobra.Command{ 17 | Use: "docs [OUTPUT_PATH]", 18 | Short: "Generate Process Compose markdown documentation", 19 | Args: cobra.ExactArgs(1), 20 | Run: func(cmd *cobra.Command, args []string) { 21 | outPath := args[0] 22 | err := doc.GenMarkdownTree(rootCmd, outPath) 23 | if err != nil { 24 | log.Fatal().Err(err).Msg("Failed to generate docs") 25 | } 26 | }, 27 | Hidden: true, 28 | } 29 | 30 | // schemaCmd represents the schema command 31 | var schemaCmd = &cobra.Command{ 32 | Use: "schema [OUTPUT_PATH]", 33 | Short: "Generate Process Compose JSON schema", 34 | Args: cobra.ExactArgs(1), 35 | Run: func(cmd *cobra.Command, args []string) { 36 | outPath := args[0] 37 | reflector := jsonschema.Reflector{ 38 | FieldNameTag: "yaml", 39 | KeyNamer: strcase.SnakeCase, 40 | AllowAdditionalProperties: true, 41 | } 42 | schema := reflector.Reflect(&types.Project{}) 43 | data, err := json.Marshal(schema) 44 | if err != nil { 45 | log.Fatal().Err(err).Msg("Failed to marshal schema") 46 | } 47 | err = os.WriteFile(outPath, data, 0644) 48 | if err != nil { 49 | log.Fatal().Err(err).Msg("Failed to write schema") 50 | } 51 | }, 52 | Hidden: true, 53 | } 54 | 55 | 56 | func init() { 57 | rootCmd.AddCommand(docsCmd) 58 | rootCmd.AddCommand(schemaCmd) 59 | } 60 | -------------------------------------------------------------------------------- /src/cmd/down.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/rs/zerolog/log" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // downCmd represents the down command 10 | var downCmd = &cobra.Command{ 11 | Use: "down", 12 | Short: "Stops all the running processes and terminates the Process Compose", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | err := getClient().ShutDownProject() 15 | if err != nil { 16 | log.Fatal().Err(err).Msg("failed to stop project") 17 | } 18 | log.Info().Msgf("Project stopped") 19 | }, 20 | } 21 | 22 | func init() { 23 | rootCmd.AddCommand(downCmd) 24 | downCmd.Flags().StringVarP(pcFlags.Address, "address", "a", *pcFlags.Address, "address of the target process compose server") 25 | } 26 | -------------------------------------------------------------------------------- /src/cmd/get.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/types" 5 | "github.com/rs/zerolog/log" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // getCmd represents the get command 10 | var getCmd = &cobra.Command{ 11 | Use: "get [PROCESS]", 12 | Short: "Get a process state", 13 | Args: cobra.MinimumNArgs(1), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | name := args[0] 16 | state, err := getClient().GetProcessState(name) 17 | if err != nil { 18 | log.Fatal().Err(err).Msg("failed to get process state") 19 | } 20 | states := types.ProcessesState{States: []types.ProcessState{*state}} 21 | if *pcFlags.OutputFormat == "" { 22 | *pcFlags.OutputFormat = "wide" 23 | } 24 | //pretty print state 25 | printStates(&states) 26 | }, 27 | } 28 | 29 | func init() { 30 | processCmd.AddCommand(getCmd) 31 | 32 | getCmd.Flags().StringVarP(pcFlags.OutputFormat, "output", "o", *pcFlags.OutputFormat, "Output format. One of: (json, wide (default))") 33 | } 34 | -------------------------------------------------------------------------------- /src/cmd/info.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/config" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // infoCmd represents the info command 10 | var infoCmd = &cobra.Command{ 11 | Use: "info", 12 | Short: "Print configuration info", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | printInfo() 15 | }, 16 | } 17 | 18 | func printInfo() { 19 | format := "%-15s %s\n" 20 | fmt.Println("Process Compose") 21 | fmt.Printf(format, "Logs:", config.GetLogFilePath()) 22 | 23 | path := config.GetShortCutsPaths(nil) 24 | if len(path) > 0 { 25 | fmt.Printf(format, "Shortcuts:", path) 26 | } 27 | fmt.Printf(format, "Custom Theme:", config.GetThemesPath()) 28 | fmt.Printf(format, "Settings:", config.GetSettingsPath()) 29 | 30 | } 31 | 32 | func init() { 33 | rootCmd.AddCommand(infoCmd) 34 | } 35 | -------------------------------------------------------------------------------- /src/cmd/is_ready.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/client" 6 | "github.com/f1bonacc1/process-compose/src/types" 7 | "github.com/rs/zerolog/log" 8 | "github.com/spf13/cobra" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // `isReadyCmd` represents the `project is-ready` command 14 | var isReadyCmd = &cobra.Command{ 15 | Use: "is-ready", 16 | Short: "Check if Process Compose project is ready (or wait for it to be ready)", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | client := getClient() 19 | 20 | if *pcFlags.WaitReady { 21 | start := time.Now() 22 | for { 23 | notReady := checkReady(client) 24 | if notReady == nil { 25 | break 26 | } 27 | log.Warn().Msgf("%s", notReady.Error()) 28 | time.Sleep(1 * time.Second) 29 | } 30 | 31 | elapsed := time.Since(start) 32 | log.Info().Msgf("Project is ready after waiting for %s", elapsed.Round(10*time.Millisecond)) 33 | } else { 34 | notReady := checkReady(client) 35 | if notReady != nil { 36 | log.Fatal().Err(notReady).Send() 37 | } 38 | } 39 | }, 40 | } 41 | 42 | type ProcessStateReason struct { 43 | State types.ProcessState 44 | Reason string 45 | } 46 | 47 | func (p *ProcessStateReason) String() string { 48 | if p.Reason == "" { 49 | return p.State.Name 50 | } else { 51 | return fmt.Sprintf("%s (%s)", p.State.Name, p.Reason) 52 | } 53 | } 54 | 55 | func checkReady(client *client.PcClient) error { 56 | states, err := client.GetProcessesState() 57 | if err != nil { 58 | return err 59 | } 60 | 61 | var notReady []ProcessStateReason = make([]ProcessStateReason, 0, len(states.States)) 62 | for _, state := range states.States { 63 | isReady, reason := state.IsReadyReason() 64 | stateReason := ProcessStateReason{ 65 | State: state, 66 | Reason: reason, 67 | } 68 | if !isReady { 69 | notReady = append(notReady, stateReason) 70 | } 71 | } 72 | 73 | if len(notReady) == 0 { 74 | log.Info().Msgf("%d processes are ready", len(states.States)) 75 | return nil 76 | } else { 77 | var explanations []string = make([]string, 0, len(notReady)) 78 | for _, process := range notReady { 79 | explanations = append(explanations, process.String()) 80 | } 81 | return fmt.Errorf("Processes are not ready:\n• %s", strings.Join(explanations, "\n• ")) 82 | } 83 | } 84 | 85 | func init() { 86 | projectCmd.AddCommand(isReadyCmd) 87 | isReadyCmd.Flags().BoolVar(pcFlags.WaitReady, "wait", false, "Wait for the project to be ready instead of exiting with an error") 88 | } 89 | -------------------------------------------------------------------------------- /src/cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/f1bonacc1/process-compose/src/app" 7 | "github.com/f1bonacc1/process-compose/src/types" 8 | "github.com/rs/zerolog/log" 9 | "os" 10 | "sort" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | // listCmd represents the list command 16 | var listCmd = &cobra.Command{ 17 | Use: "list", 18 | Short: "List available processes", 19 | Aliases: []string{"ls"}, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | states, err := getClient().GetRemoteProcessesState() 22 | if err != nil { 23 | log.Fatal().Err(err).Msg("failed to list processes") 24 | } 25 | //sort states by name 26 | sort.Slice(states.States, func(i, j int) bool { 27 | return states.States[i].Name < states.States[j].Name 28 | }) 29 | 30 | printStates(states) 31 | 32 | }, 33 | } 34 | 35 | func printStates(states *types.ProcessesState) { 36 | switch *pcFlags.OutputFormat { 37 | case "json": 38 | b, err := json.MarshalIndent(states.States, "", "\t") 39 | if err != nil { 40 | log.Fatal().Err(err).Msg("failed to marshal processes") 41 | } 42 | os.Stdout.Write(b) 43 | case "wide": 44 | app.PrintStatesAsTable(states.States) 45 | case "": 46 | for _, state := range states.States { 47 | fmt.Println(state.Name) 48 | } 49 | default: 50 | log.Fatal().Msgf("unknown output format %s", *pcFlags.OutputFormat) 51 | } 52 | } 53 | 54 | func init() { 55 | processCmd.AddCommand(listCmd) 56 | rootCmd.AddCommand(listCmd) 57 | 58 | listCmd.Flags().StringVarP(pcFlags.OutputFormat, "output", "o", *pcFlags.OutputFormat, "Output format. One of: (json, wide)") 59 | } 60 | -------------------------------------------------------------------------------- /src/cmd/ports.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/rs/zerolog/log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // portsCmd represents the ports command 11 | var portsCmd = &cobra.Command{ 12 | Use: "ports [PROCESS]", 13 | Short: "Get the ports that a process is listening on", 14 | Args: cobra.ExactArgs(1), 15 | Run: func(cmd *cobra.Command, args []string) { 16 | name := args[0] 17 | ports, err := getClient().GetProcessPorts(name) 18 | if err != nil { 19 | log.Fatal().Err(err).Msgf("failed to get process %s ports", name) 20 | return 21 | } 22 | log.Info().Msgf("Process %s TCP ports: %v", name, ports.TcpPorts) 23 | fmt.Printf("Process %s TCP ports: %v\n", name, ports.TcpPorts) 24 | }, 25 | } 26 | 27 | func init() { 28 | processCmd.AddCommand(portsCmd) 29 | } 30 | -------------------------------------------------------------------------------- /src/cmd/process.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // processCmd represents the process command 8 | var processCmd = &cobra.Command{ 9 | Use: "process", 10 | Short: "Execute operations on the available processes", 11 | Args: cobra.MinimumNArgs(1), 12 | } 13 | 14 | func init() { 15 | rootCmd.AddCommand(processCmd) 16 | processCmd.PersistentFlags().StringVarP(pcFlags.Address, "address", "a", *pcFlags.Address, "address of the target process compose server") 17 | } 18 | -------------------------------------------------------------------------------- /src/cmd/project.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // projectCmd represents the project command 11 | var projectCmd = &cobra.Command{ 12 | Use: "project", 13 | Short: "Execute operations on a running Process Compose project", 14 | } 15 | 16 | func init() { 17 | rootCmd.AddCommand(projectCmd) 18 | projectCmd.PersistentFlags().StringVarP(pcFlags.Address, "address", "a", *pcFlags.Address, "address of the target process compose server") 19 | } 20 | -------------------------------------------------------------------------------- /src/cmd/project_runner_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "github.com/rs/zerolog/log" 8 | "os" 9 | "os/exec" 10 | "syscall" 11 | ) 12 | 13 | func runInDetachedMode() { 14 | log.Info().Msg("Running in detached mode") 15 | fmt.Println("Starting Process Compose in detached mode. Use 'process-compose attach' to connect to it or 'process-compose down' to stop it") 16 | //remove detached flag 17 | for i, arg := range os.Args { 18 | if arg == "-D" || arg == "--detached" || arg == "--detached-with-tui" { 19 | os.Args = append(os.Args[:i], os.Args[i+1:]...) 20 | break 21 | } 22 | } 23 | // Prepare to launch the background process 24 | os.Args = append(os.Args, "-t=false") 25 | cmd := exec.Command(os.Args[0], os.Args[1:]...) 26 | cmd.SysProcAttr = &syscall.SysProcAttr{ 27 | Setsid: true, // Detach from terminal 28 | } 29 | 30 | // Redirect standard file descriptors to /dev/null 31 | cmd.Stdin = nil 32 | cmd.Stdout, _ = os.OpenFile("/dev/null", os.O_RDWR, 0) 33 | cmd.Stderr, _ = os.OpenFile("/dev/null", os.O_RDWR, 0) 34 | 35 | // Start the process in the background 36 | if err := cmd.Start(); err != nil { 37 | panic(err) 38 | } 39 | if *pcFlags.IsDetachedWithTui { 40 | startTui(getClient(), false) 41 | } 42 | // Exit the parent process 43 | os.Exit(0) 44 | } 45 | -------------------------------------------------------------------------------- /src/cmd/project_runner_windows.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "github.com/rs/zerolog/log" 4 | 5 | func runInDetachedMode() { 6 | log.Fatal().Msg("Running in detached mode is not supported on Windows") 7 | } 8 | -------------------------------------------------------------------------------- /src/cmd/restart.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/rs/zerolog/log" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // restartCmd represents the restart command 10 | var restartCmd = &cobra.Command{ 11 | Use: "restart [PROCESS]", 12 | Short: "Restart a process", 13 | Args: cobra.ExactArgs(1), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | name := args[0] 16 | err := getClient().RestartProcess(name) 17 | if err != nil { 18 | log.Fatal().Err(err).Msgf("failed to restart process %s", name) 19 | } 20 | fmt.Printf("Process %s restarted\n", name) 21 | }, 22 | } 23 | 24 | func init() { 25 | processCmd.AddCommand(restartCmd) 26 | } 27 | -------------------------------------------------------------------------------- /src/cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | "os" 7 | ) 8 | 9 | // runCmd represents the up command 10 | var runCmd = &cobra.Command{ 11 | Use: "run PROCESS [flags] -- [process_args]", 12 | Short: "Run PROCESS in the foreground, and its dependencies in the background", 13 | Long: `Run selected process with std(in|out|err) attached, while other processes run in the background. 14 | Command line arguments, provided after --, are passed to the PROCESS.`, 15 | Args: cobra.MinimumNArgs(1), 16 | // Args: cobra.ExactArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | *pcFlags.IsTuiEnabled = false 19 | 20 | processName := args[0] 21 | 22 | if len(args) > 1 { 23 | argsLenAtDash := cmd.ArgsLenAtDash() 24 | if argsLenAtDash != 1 { 25 | message := "Extra positional arguments provided! To pass args to PROCESS, separate them from process-compose arguments with: --" 26 | fmt.Println(message) 27 | os.Exit(1) 28 | } 29 | args = args[argsLenAtDash:] 30 | } else { 31 | // Clease args as they will contain the processName 32 | args = []string{} 33 | } 34 | 35 | runner := getProjectRunner( 36 | []string{processName}, 37 | *pcFlags.NoDependencies, 38 | processName, 39 | args, 40 | ) 41 | 42 | err := waitForProjectAndServer(!*pcFlags.IsTuiEnabled, runner) 43 | handleErrorAndExit(err) 44 | }, 45 | } 46 | 47 | func init() { 48 | rootCmd.AddCommand(runCmd) 49 | 50 | runCmd.Flags().BoolVarP(pcFlags.NoDependencies, "no-deps", "", *pcFlags.NoDependencies, "don't start dependent processes") 51 | runCmd.Flags().AddFlag(rootCmd.Flags().Lookup("config")) 52 | runCmd.Flags().AddFlag(rootCmd.Flags().Lookup("disable-dotenv")) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/cmd/scale.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/rs/zerolog/log" 5 | "strconv" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // scaleCmd represents the scale command 11 | var scaleCmd = &cobra.Command{ 12 | Use: "scale [PROCESS] [COUNT]", 13 | Short: "Scale a process to a given count", 14 | Args: cobra.ExactArgs(2), 15 | Run: func(cmd *cobra.Command, args []string) { 16 | name := args[0] 17 | count, err := strconv.Atoi(args[1]) 18 | if err != nil { 19 | log.Fatal().Err(err).Msg("second argument must be an integer") 20 | } 21 | err = getClient().ScaleProcess(name, count) 22 | if err != nil { 23 | log.Fatal().Err(err).Msgf("failed to scale process %s", name) 24 | } 25 | log.Info().Msgf("Process %s scaled to %s", name, args[1]) 26 | }, 27 | } 28 | 29 | func init() { 30 | processCmd.AddCommand(scaleCmd) 31 | } 32 | -------------------------------------------------------------------------------- /src/cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/rs/zerolog/log" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // startCmd represents the start command 10 | var startCmd = &cobra.Command{ 11 | Use: "start [PROCESS]", 12 | Short: "Start a process", 13 | Args: cobra.ExactArgs(1), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | name := args[0] 16 | err := getClient().StartProcess(name) 17 | if err != nil { 18 | log.Fatal().Err(err).Msgf("failed to start process %s", name) 19 | 20 | } 21 | fmt.Printf("Process %s started\n", name) 22 | }, 23 | } 24 | 25 | func init() { 26 | processCmd.AddCommand(startCmd) 27 | } 28 | -------------------------------------------------------------------------------- /src/cmd/state.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | "github.com/fatih/color" 7 | "github.com/rs/zerolog/log" 8 | "github.com/spf13/cobra" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | var ( 15 | withMemoryUsage = false 16 | ) 17 | 18 | // stateCmd represents the state command 19 | var stateCmd = &cobra.Command{ 20 | Use: "state", 21 | Short: "Get Process Compose project state", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | state, err := getClient().GetProjectState(withMemoryUsage) 24 | if err != nil { 25 | log.Fatal().Err(err).Msg("failed to get project state") 26 | } 27 | //pretty print state 28 | printState(state) 29 | }, 30 | } 31 | 32 | func printState(state *types.ProjectState) { 33 | col := color.New(color.FgGreen) 34 | col.EnableColor() 35 | green := col.SprintFunc() 36 | 37 | longestKey := len("Running Processes") 38 | printStateLine("Hostname", state.HostName, longestKey, green) 39 | printStateLine("User", state.UserName, longestKey, green) 40 | printStateLine("Version", state.Version, longestKey, green) 41 | printStateLine("Up Time", state.UpTime.Round(time.Second).String(), longestKey, green) 42 | printStateLine("Processes", strconv.Itoa(state.ProcessNum), longestKey, green) 43 | printStateLine("Running Processes", strconv.Itoa(state.RunningProcessNum), longestKey, green) 44 | 45 | fmt.Printf("%s:\n", green("File Names")) 46 | for _, file := range state.FileNames { 47 | fmt.Printf("\t - %s\n", file) 48 | } 49 | 50 | if state.MemoryState != nil { 51 | printStateLine("Allocated MB", strconv.FormatUint(state.MemoryState.Allocated, 10), longestKey, green) 52 | printStateLine("Total Alloc MB", strconv.FormatUint(state.MemoryState.TotalAllocated, 10), longestKey, green) 53 | printStateLine("System MB", strconv.FormatUint(state.MemoryState.SystemMemory, 10), longestKey, green) 54 | printStateLine("GC Cycles", strconv.Itoa(int(state.MemoryState.GcCycles)), longestKey, green) 55 | } 56 | } 57 | func printStateLine(key, value string, longestKey int, green func(a ...interface{}) string) { 58 | dotPads := longestKey - len(key) 59 | padding := strings.Repeat(" ", dotPads) 60 | 61 | fmt.Printf("%s:%s %s\n", green(key), padding, value) 62 | } 63 | 64 | func init() { 65 | projectCmd.AddCommand(stateCmd) 66 | stateCmd.Flags().BoolVar(&withMemoryUsage, "with-memory", false, "check memory usage") 67 | } 68 | -------------------------------------------------------------------------------- /src/cmd/truncate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/rs/zerolog/log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // truncateCmd represents the truncate command 11 | var truncateCmd = &cobra.Command{ 12 | Use: "truncate [PROCESS]", 13 | Short: "Truncate the logs for a running or stopped process", 14 | Args: cobra.ExactArgs(1), 15 | Run: func(cmd *cobra.Command, args []string) { 16 | name := args[0] 17 | err := getClient().TruncateProcessLogs(name) 18 | if err != nil { 19 | log.Fatal().Err(err).Msgf("failed to truncate logs for process %s", name) 20 | 21 | } 22 | fmt.Printf("Process %s logs truncated\n", name) 23 | }, 24 | } 25 | 26 | func init() { 27 | logsCmd.AddCommand(truncateCmd) 28 | } 29 | -------------------------------------------------------------------------------- /src/cmd/up.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/admitter" 5 | "github.com/spf13/cobra" 6 | "runtime" 7 | ) 8 | 9 | // upCmd represents the up command 10 | var upCmd = &cobra.Command{ 11 | Use: "up [PROCESS...]", 12 | Short: "Run process compose project", 13 | Long: `Run all the process compose processes. 14 | If one or more process names are passed as arguments, 15 | will start them and their dependencies only`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | runProjectCmd(args) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(upCmd) 23 | 24 | nsAdmitter := &admitter.NamespaceAdmitter{} 25 | opts.AddAdmitter(nsAdmitter) 26 | 27 | upCmd.Flags().BoolVarP(pcFlags.NoDependencies, "no-deps", "", *pcFlags.NoDependencies, "don't start dependent processes") 28 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("namespace")) 29 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("config")) 30 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("env")) 31 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("ref-rate")) 32 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("tui")) 33 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("hide-disabled")) 34 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("disable-dotenv")) 35 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("keep-tui")) 36 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("keep-project")) 37 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("shortcuts")) 38 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("logs-truncate")) 39 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("dry-run")) 40 | upCmd.Flags().AddFlag(commonFlags.Lookup(flagReverse)) 41 | upCmd.Flags().AddFlag(commonFlags.Lookup(flagSort)) 42 | upCmd.Flags().AddFlag(commonFlags.Lookup(flagTheme)) 43 | 44 | if runtime.GOOS != "windows" { 45 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("detached")) 46 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("detached-with-tui")) 47 | upCmd.Flags().AddFlag(rootCmd.Flags().Lookup("detach-on-success")) 48 | } 49 | _ = upCmd.Flags().MarkDeprecated("keep-tui", "use --keep-project instead") 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/config" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // versionCmd represents the version command 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print version and build info", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | if *pcFlags.ShortVersion { 15 | fmt.Println(config.Version) 16 | return 17 | } 18 | printVersion() 19 | }, 20 | } 21 | 22 | func printVersion() { 23 | format := "%-15s %s\n" 24 | fmt.Println("Process Compose") 25 | fmt.Printf(format, "Version:", config.Version) 26 | fmt.Printf(format, "Commit:", config.Commit) 27 | fmt.Printf(format, "Date (UTC):", config.Date) 28 | fmt.Printf(format, "License:", config.License) 29 | fmt.Printf(format, "Discord:", config.Discord) 30 | fmt.Println("\nWritten by Eugene Berger") 31 | } 32 | 33 | func init() { 34 | rootCmd.AddCommand(versionCmd) 35 | versionCmd.Flags().BoolVar(pcFlags.ShortVersion, "short", *pcFlags.ShortVersion, "Print only version") 36 | } 37 | -------------------------------------------------------------------------------- /src/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "github.com/rs/zerolog/log" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | ) 10 | 11 | func BuildCommand(cmd string, args []string) *CmdWrapper { 12 | return &CmdWrapper{ 13 | cmd: exec.Command(cmd, args...), 14 | } 15 | } 16 | 17 | func BuildPtyCommand(cmd string, args []string) *CmdWrapperPty { 18 | return &CmdWrapperPty{ 19 | CmdWrapper: BuildCommand(cmd, args), 20 | } 21 | } 22 | 23 | func BuildCommandContext(ctx context.Context, shellCmd string) *CmdWrapper { 24 | return &CmdWrapper{ 25 | cmd: exec.CommandContext(ctx, getRunnerShell(), getRunnerArg(), shellCmd), 26 | } 27 | } 28 | 29 | func BuildCommandShellArgContext(ctx context.Context, shell ShellConfig, cmd string) *CmdWrapper { 30 | return &CmdWrapper{ 31 | cmd: exec.CommandContext(ctx, shell.ShellCommand, shell.ShellArgument, cmd), 32 | } 33 | } 34 | 35 | func getRunnerShell() string { 36 | shell, ok := os.LookupEnv("COMPOSE_SHELL") 37 | if !ok { 38 | if runtime.GOOS == "windows" { 39 | shell = "cmd" 40 | } else { 41 | shell = "bash" 42 | } 43 | } 44 | return shell 45 | } 46 | 47 | func getRunnerArg() string { 48 | arg := "-c" 49 | if runtime.GOOS == "windows" { 50 | arg = "/C" 51 | } 52 | return arg 53 | } 54 | 55 | func getElevatedRunnerCmd() string { 56 | shell := "sudo" 57 | if runtime.GOOS == "windows" { 58 | shell = "runas" 59 | } 60 | return shell 61 | } 62 | 63 | func getElevatedRunnerArg() string { 64 | arg := "-S" 65 | if runtime.GOOS == "windows" { 66 | arg = "/user:Administrator" 67 | } 68 | return arg 69 | } 70 | 71 | func DefaultShellConfig() *ShellConfig { 72 | return &ShellConfig{ 73 | ShellCommand: getRunnerShell(), 74 | ShellArgument: getRunnerArg(), 75 | ElevatedShellCmd: getElevatedRunnerCmd(), 76 | ElevatedShellArg: getElevatedRunnerArg(), 77 | } 78 | } 79 | 80 | func ValidateShellConfig(shell ShellConfig) { 81 | _, err := exec.LookPath(shell.ShellCommand) 82 | if err != nil { 83 | log.Fatal().Err(err).Msgf("Couldn't find %s", shell.ShellCommand) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/command/command_wrapper.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | type CmdWrapper struct { 10 | cmd *exec.Cmd 11 | } 12 | 13 | func (c *CmdWrapper) Start() error { 14 | return c.cmd.Start() 15 | } 16 | 17 | func (c *CmdWrapper) Run() error { 18 | return c.cmd.Run() 19 | } 20 | 21 | func (c *CmdWrapper) Wait() error { 22 | return c.cmd.Wait() 23 | } 24 | 25 | func (c *CmdWrapper) ExitCode() int { 26 | return c.cmd.ProcessState.ExitCode() 27 | } 28 | 29 | func (c *CmdWrapper) Pid() int { 30 | return c.cmd.Process.Pid 31 | } 32 | 33 | func (c *CmdWrapper) StdoutPipe() (io.ReadCloser, error) { 34 | return c.cmd.StdoutPipe() 35 | } 36 | 37 | func (c *CmdWrapper) StderrPipe() (io.ReadCloser, error) { 38 | return c.cmd.StderrPipe() 39 | } 40 | 41 | func (c *CmdWrapper) StdinPipe() (io.WriteCloser, error) { 42 | return c.cmd.StdinPipe() 43 | } 44 | 45 | func (c *CmdWrapper) AttachIo() { 46 | c.cmd.Stdin = os.Stdin 47 | c.cmd.Stdout = os.Stdout 48 | c.cmd.Stderr = os.Stderr 49 | } 50 | 51 | func (c *CmdWrapper) SetEnv(env []string) { 52 | c.cmd.Env = env 53 | } 54 | 55 | func (c *CmdWrapper) SetDir(dir string) { 56 | c.cmd.Dir = dir 57 | } 58 | 59 | func (c *CmdWrapper) Output() ([]byte, error) { 60 | return c.cmd.Output() 61 | } 62 | 63 | func (c *CmdWrapper) CombinedOutput() ([]byte, error) { 64 | return c.cmd.CombinedOutput() 65 | } 66 | -------------------------------------------------------------------------------- /src/command/command_wrapper_pty.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/creack/pty" 7 | "golang.org/x/term" 8 | "io" 9 | "os" 10 | ) 11 | 12 | type CmdWrapperPty struct { 13 | *CmdWrapper 14 | ptmx *os.File 15 | } 16 | 17 | func (c *CmdWrapperPty) Start() (err error) { 18 | if c.ptmx != nil { 19 | return nil 20 | } 21 | c.ptmx, err = pty.Start(c.cmd) 22 | if err != nil { 23 | return fmt.Errorf("error starting PTY command: %w", err) 24 | } 25 | // No need to capture/restore old state, because we close the PTY when we're done. 26 | _, err = term.MakeRaw(int(c.ptmx.Fd())) 27 | if err != nil { 28 | return fmt.Errorf("error putting PTY into raw mode: %w", err) 29 | } 30 | return err 31 | } 32 | 33 | func (c *CmdWrapperPty) Wait() error { 34 | defer c.ptmx.Close() 35 | return c.cmd.Wait() 36 | } 37 | 38 | func (c *CmdWrapperPty) StdoutPipe() (io.ReadCloser, error) { 39 | if c.ptmx == nil { 40 | err := c.Start() 41 | if err != nil { 42 | return nil, err 43 | } 44 | } 45 | return c.ptmx, nil 46 | } 47 | 48 | func (c *CmdWrapperPty) StderrPipe() (io.ReadCloser, error) { 49 | return nil, errors.New("not supported in PTY") 50 | } 51 | func (c *CmdWrapperPty) StdinPipe() (io.WriteCloser, error) { 52 | if c.ptmx == nil { 53 | err := c.Start() 54 | if err != nil { 55 | return nil, err 56 | } 57 | } 58 | return c.ptmx, nil 59 | } 60 | 61 | func (c *CmdWrapperPty) SetCmdArgs() { 62 | //c.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 63 | } 64 | -------------------------------------------------------------------------------- /src/command/commander.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "io" 4 | 5 | type Commander interface { 6 | Stop(sig int, _parentOnly bool) error 7 | SetCmdArgs() 8 | Start() error 9 | Run() error 10 | Wait() error 11 | ExitCode() int 12 | Pid() int 13 | StdoutPipe() (io.ReadCloser, error) 14 | StderrPipe() (io.ReadCloser, error) 15 | StdinPipe() (io.WriteCloser, error) 16 | AttachIo() 17 | SetEnv(env []string) 18 | SetDir(dir string) 19 | Output() ([]byte, error) 20 | CombinedOutput() ([]byte, error) 21 | } 22 | -------------------------------------------------------------------------------- /src/command/config.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | type ShellConfig struct { 4 | ShellCommand string `yaml:"shell_command"` 5 | ShellArgument string `yaml:"shell_argument"` 6 | ElevatedShellCmd string `yaml:"elevated_shell_command,omitempty"` 7 | ElevatedShellArg string `yaml:"elevated_shell_argument,omitempty"` 8 | } 9 | -------------------------------------------------------------------------------- /src/command/mock_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | type MockCommand struct { 9 | dir string 10 | env []string 11 | cancel context.CancelFunc 12 | stopChan chan struct{} 13 | infoNoiseMaker *noiseMaker 14 | errNoiseMaker *noiseMaker 15 | } 16 | 17 | func NewMockCommand() *MockCommand { 18 | return &MockCommand{ 19 | stopChan: make(chan struct{}), 20 | infoNoiseMaker: newNoiseMaker("info noise"), 21 | errNoiseMaker: newNoiseMaker("error noise"), 22 | } 23 | } 24 | 25 | func (c *MockCommand) Start() error { 26 | ctx, cancel := context.WithCancel(context.Background()) 27 | c.cancel = cancel 28 | go c.infoNoiseMaker.Run(ctx) 29 | return nil 30 | } 31 | func (c *MockCommand) Stop(_ int) error { 32 | c.stopChan <- struct{}{} 33 | c.cancel() 34 | return nil 35 | } 36 | 37 | func (c *MockCommand) SetCmdArgs() { 38 | 39 | } 40 | 41 | func (c *MockCommand) Wait() error { 42 | <-c.stopChan 43 | return nil 44 | } 45 | 46 | func (c *MockCommand) ExitCode() int { 47 | return 0 48 | } 49 | 50 | func (c *MockCommand) Pid() int { 51 | return 123456 52 | } 53 | 54 | func (c *MockCommand) StdoutPipe() (io.ReadCloser, error) { 55 | return c.infoNoiseMaker, nil 56 | } 57 | 58 | func (c *MockCommand) StderrPipe() (io.ReadCloser, error) { 59 | return c.errNoiseMaker, nil 60 | } 61 | 62 | func (c *MockCommand) SetEnv(env []string) { 63 | c.env = env 64 | } 65 | 66 | func (c *MockCommand) SetDir(dir string) { 67 | c.dir = dir 68 | } 69 | 70 | func (c *MockCommand) Output() ([]byte, error) { 71 | return nil, nil 72 | } 73 | -------------------------------------------------------------------------------- /src/command/noise_maker.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type noiseMaker struct { 10 | ticker *time.Ticker 11 | noiseChan chan []byte 12 | noiseData []byte 13 | } 14 | 15 | func newNoiseMaker(noiseData string) *noiseMaker { 16 | return &noiseMaker{ 17 | ticker: time.NewTicker(time.Second), 18 | noiseChan: make(chan []byte, 10), 19 | noiseData: []byte(noiseData), 20 | } 21 | } 22 | 23 | func (n *noiseMaker) Run(ctx context.Context) { 24 | for { 25 | select { 26 | case t := <-n.ticker.C: 27 | data := append(n.noiseData, " "+t.String()+"\n"...) 28 | n.noiseChan <- data 29 | case <-ctx.Done(): 30 | break 31 | } 32 | } 33 | } 34 | 35 | func (n *noiseMaker) Read(p []byte) (size int, err error) { 36 | data, ok := <-n.noiseChan 37 | if !ok { 38 | return 0, io.EOF 39 | } 40 | copy(p, data) 41 | return len(p), nil 42 | } 43 | 44 | func (n *noiseMaker) Close() error { 45 | n.ticker.Stop() 46 | close(n.noiseChan) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /src/command/stopper_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package command 4 | 5 | import ( 6 | "github.com/rs/zerolog/log" 7 | "syscall" 8 | ) 9 | 10 | const ( 11 | min_sig = 1 12 | max_sig = 31 13 | ) 14 | 15 | func (c *CmdWrapper) Stop(sig int, parentOnly bool) error { 16 | if c.cmd == nil { 17 | return nil 18 | } 19 | if sig < min_sig || sig > max_sig { 20 | sig = int(syscall.SIGTERM) 21 | } 22 | 23 | log. 24 | Debug(). 25 | Int("pid", c.Pid()). 26 | Int("signal", sig). 27 | Bool("parentOnly", parentOnly). 28 | Msg("Stop Unix process.") 29 | 30 | if parentOnly { 31 | return c.cmd.Process.Signal(syscall.Signal(sig)) 32 | } 33 | 34 | pgid, err := syscall.Getpgid(c.Pid()) 35 | if err == nil { 36 | return syscall.Kill(-pgid, syscall.Signal(sig)) 37 | } 38 | 39 | return err 40 | } 41 | 42 | func (c *CmdWrapper) SetCmdArgs() { 43 | c.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 44 | } 45 | -------------------------------------------------------------------------------- /src/command/stopper_windows.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "os/exec" 5 | "strconv" 6 | ) 7 | 8 | func (c *CmdWrapper) Stop(sig int, _parentOnly bool) error { 9 | //p.command.Process.Kill() 10 | kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(c.Pid())) 11 | return kill.Run() 12 | } 13 | 14 | func (c *CmdWrapper) SetCmdArgs() { 15 | //empty for windows 16 | } 17 | -------------------------------------------------------------------------------- /src/config/color.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gdamore/tcell/v2" 7 | ) 8 | 9 | const ( 10 | // DefaultColor represents a default color. 11 | DefaultColor Color = "default" 12 | 13 | // TransparentColor represents the terminal bg color. 14 | TransparentColor Color = "-" 15 | ) 16 | 17 | // Colors tracks multiple colors. 18 | type Colors []Color 19 | 20 | // Colors converts series string colors to colors. 21 | func (c Colors) Colors() []tcell.Color { 22 | cc := make([]tcell.Color, 0, len(c)) 23 | for _, color := range c { 24 | cc = append(cc, color.Color()) 25 | } 26 | 27 | return cc 28 | } 29 | 30 | // Color represents a color. 31 | type Color string 32 | 33 | // NewColor returns a new color. 34 | func NewColor(c string) Color { 35 | return Color(c) 36 | } 37 | 38 | // String returns color as string. 39 | func (c Color) String() string { 40 | if c.isHex() { 41 | return string(c) 42 | } 43 | if c == DefaultColor { 44 | return "-" 45 | } 46 | col := c.Color().TrueColor().Hex() 47 | if col < 0 { 48 | return "-" 49 | } 50 | 51 | return fmt.Sprintf("#%06x", col) 52 | } 53 | 54 | func (c Color) isHex() bool { 55 | return len(c) == 7 && c[0] == '#' 56 | } 57 | 58 | // Color returns a view color. 59 | func (c Color) Color() tcell.Color { 60 | if c == DefaultColor { 61 | return tcell.ColorDefault 62 | } 63 | if c.isHex() { 64 | return tcell.GetColor(string(c)).TrueColor() 65 | } else { 66 | return tcell.GetColor(string(c)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "embed" 5 | ) 6 | 7 | var ( 8 | 9 | ////go:embed templates/hotkeys.yaml 10 | // hotkeysTpl tracks hotkeys default config template 11 | //hotkeysTpl []byte 12 | 13 | //go:embed themes/*-theme.yaml 14 | themesFolder embed.FS 15 | ) 16 | -------------------------------------------------------------------------------- /src/config/settings.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/rs/zerolog/log" 5 | "gopkg.in/yaml.v3" 6 | "os" 7 | ) 8 | 9 | type ( 10 | Sort struct { 11 | By string `yaml:"by"` 12 | IsReversed bool `yaml:"isReversed"` 13 | } 14 | Settings struct { 15 | Theme string `yaml:"theme"` 16 | Sort Sort `yaml:"sort"` 17 | DisableExitConfirmation bool `yaml:"disable_exit_confirmation"` 18 | } 19 | ) 20 | 21 | func NewSettings() *Settings { 22 | return &Settings{ 23 | Theme: DefaultThemeName, 24 | Sort: Sort{ 25 | By: DefaultSortColumn, 26 | IsReversed: false, 27 | }, 28 | } 29 | } 30 | 31 | func (s *Settings) Load() *Settings { 32 | filePath := GetSettingsPath() 33 | if !fileExists(filePath) { 34 | return s 35 | } 36 | b, err := os.ReadFile(filePath) 37 | if err != nil { 38 | log.Warn().Err(err).Msgf("Error reading settings file %s", filePath) 39 | return s 40 | } 41 | err = yaml.Unmarshal(b, s) 42 | if err != nil { 43 | log.Warn().Err(err).Msgf("Error parsing settings file %s", filePath) 44 | return s 45 | } 46 | log.Debug().Msgf("Loaded settings from %s", filePath) 47 | return s 48 | } 49 | 50 | func (s *Settings) Save() error { 51 | filePath := GetSettingsPath() 52 | b, err := yaml.Marshal(s) 53 | if err != nil { 54 | log.Warn().Err(err).Msgf("Error marshalling settings file %s", filePath) 55 | return err 56 | } 57 | err = os.WriteFile(filePath, b, 0644) 58 | return err 59 | } 60 | 61 | func fileExists(filename string) bool { 62 | info, err := os.Stat(filename) 63 | if os.IsNotExist(err) { 64 | return false 65 | } 66 | if err != nil { 67 | log.Warn().Err(err).Msgf("Error checking file %s", filename) 68 | return false 69 | } 70 | return !info.IsDir() 71 | } 72 | -------------------------------------------------------------------------------- /src/config/themes/catppuccin-frappe-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'Catppuccin Frappe' 3 | body: 4 | fgColor: '#c6d0f5' 5 | bgColor: '#303446' 6 | secondaryTextColor: '#ca9ee6' 7 | tertiaryTextColor: '#ca9ee6' 8 | borderColor: '#949cbb' 9 | stat_table: 10 | keyFgColor: '#ca9ee6' 11 | valueFgColor: '#c6d0f5' 12 | logoColor: '#ca9ee6' 13 | proc_table: 14 | fgColor: '#c6d0f5' 15 | fgWarning: '#ef9f76' 16 | fgPending: '#949cbb' 17 | fgCompleted: '#a6d189' 18 | fgError: '#e78284' 19 | headerFgColor: '#ca9ee6' 20 | help: 21 | fgColor: '#c6d0f5' 22 | keyColor: '#ca9ee6' 23 | hlColor: '#51576d' 24 | categoryFgColor: '#f4b8e4' 25 | dialog: 26 | fgColor: '#f4b8e4' 27 | bgColor: '#ca9ee6' 28 | buttonFgColor: '#232634' 29 | buttonBgColor: '#ca9ee6' 30 | buttonFocusFgColor: '#232634' 31 | buttonFocusBgColor: '#ca9ee6' 32 | labelFgColor: '#f2d5cf' 33 | fieldFgColor: '#c6d0f5' 34 | fieldBgColor: '#626880' 35 | -------------------------------------------------------------------------------- /src/config/themes/catppuccin-latte-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'Catppuccin Latte' 3 | body: 4 | fgColor: '#4c4f69' 5 | bgColor: '#eff1f5' 6 | secondaryTextColor: '#8839ef' 7 | tertiaryTextColor: '#8839ef' 8 | borderColor: '#7c7f93' 9 | stat_table: 10 | keyFgColor: '#8839ef' 11 | valueFgColor: '#4c4f69' 12 | logoColor: '#8839ef' 13 | proc_table: 14 | fgColor: '#4c4f69' 15 | fgWarning: '#fe640b' 16 | fgPending: '#7c7f93' 17 | fgCompleted: '#40a02b' 18 | fgError: '#d20f39' 19 | headerFgColor: '#8839ef' 20 | help: 21 | fgColor: '#4c4f69' 22 | keyColor: '#8839ef' 23 | hlColor: '#bcc0cc' 24 | categoryFgColor: '#ea76cb' 25 | dialog: 26 | fgColor: '#ea76cb' 27 | bgColor: '#8839ef' 28 | buttonFgColor: '#dce0e8' 29 | buttonBgColor: '#8839ef' 30 | buttonFocusFgColor: '#dce0e8' 31 | buttonFocusBgColor: '#8839ef' 32 | labelFgColor: '#dc8a78' 33 | fieldFgColor: '#4c4f69' 34 | fieldBgColor: '#acb0be' 35 | -------------------------------------------------------------------------------- /src/config/themes/catppuccin-macchiato-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'Catppuccin Macchiato' 3 | body: 4 | fgColor: '#cad3f5' 5 | bgColor: '#24273a' 6 | secondaryTextColor: '#c6a0f6' 7 | tertiaryTextColor: '#c6a0f6' 8 | borderColor: '#939ab7' 9 | stat_table: 10 | keyFgColor: '#c6a0f6' 11 | valueFgColor: '#cad3f5' 12 | logoColor: '#c6a0f6' 13 | proc_table: 14 | fgColor: '#cad3f5' 15 | fgWarning: '#f5a97f' 16 | fgPending: '#939ab7' 17 | fgCompleted: '#a6da95' 18 | fgError: '#ed8796' 19 | headerFgColor: '#c6a0f6' 20 | help: 21 | fgColor: '#cad3f5' 22 | keyColor: '#c6a0f6' 23 | hlColor: '#494d64' 24 | categoryFgColor: '#f5bde6' 25 | dialog: 26 | fgColor: '#f5bde6' 27 | bgColor: '#c6a0f6' 28 | buttonFgColor: '#181926' 29 | buttonBgColor: '#c6a0f6' 30 | buttonFocusFgColor: '#181926' 31 | buttonFocusBgColor: '#c6a0f6' 32 | labelFgColor: '#f4dbd6' 33 | fieldFgColor: '#cad3f5' 34 | fieldBgColor: '#5b6078' 35 | -------------------------------------------------------------------------------- /src/config/themes/catppuccin-mocha-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'Catppuccin Mocha' 3 | body: 4 | fgColor: '#cdd6f4' 5 | bgColor: '#1e1e2e' 6 | secondaryTextColor: '#cba6f7' 7 | tertiaryTextColor: '#cba6f7' 8 | borderColor: '#9399b2' 9 | stat_table: 10 | keyFgColor: '#cba6f7' 11 | valueFgColor: '#cdd6f4' 12 | logoColor: '#cba6f7' 13 | proc_table: 14 | fgColor: '#cdd6f4' 15 | fgWarning: '#fab387' 16 | fgPending: '#9399b2' 17 | fgCompleted: '#a6e3a1' 18 | fgError: '#f38ba8' 19 | headerFgColor: '#cba6f7' 20 | help: 21 | fgColor: '#cdd6f4' 22 | keyColor: '#cba6f7' 23 | hlColor: '#45475a' 24 | categoryFgColor: '#f5c2e7' 25 | dialog: 26 | fgColor: '#f5c2e7' 27 | bgColor: '#cba6f7' 28 | buttonFgColor: '#11111b' 29 | buttonBgColor: '#cba6f7' 30 | buttonFocusFgColor: '#11111b' 31 | buttonFocusBgColor: '#cba6f7' 32 | labelFgColor: '#f5e0dc' 33 | fieldFgColor: '#cdd6f4' 34 | fieldBgColor: '#585b70' 35 | -------------------------------------------------------------------------------- /src/config/themes/cobalt-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: Cobalt 3 | body: 4 | fgColor: white 5 | bgColor: '#193549' 6 | secondaryTextColor: '#FDBC4B' 7 | tertiaryTextColor: green 8 | borderColor: lightskyblue 9 | stat_table: 10 | keyFgColor: '#FDBC4B' 11 | valueFgColor: white 12 | logoColor: '#FDBC4B' 13 | proc_table: 14 | fgColor: lightskyblue 15 | fgWarning: '#FDBC4B' 16 | fgPending: grey 17 | fgCompleted: lightgreen 18 | fgError: red 19 | headerFgColor: '#FDBC4B' 20 | help: 21 | fgColor: black 22 | keyColor: white 23 | hlColor: '#FDBC4B' 24 | categoryFgColor: lightskyblue 25 | dialog: 26 | fgColor: cadetblue 27 | bgColor: black 28 | contrastBgColor: blue 29 | attentionBgColor: red 30 | buttonFgColor: black 31 | buttonBgColor: lightskyblue 32 | buttonFocusFgColor: black 33 | buttonFocusBgColor: dodgerblue 34 | labelFgColor: '#FDBC4B' 35 | fieldFgColor: black 36 | fieldBgColor: lightskyblue 37 | -------------------------------------------------------------------------------- /src/config/themes/default-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: "Default" 3 | body: 4 | fgColor: white 5 | bgColor: black 6 | secondaryTextColor: yellow 7 | tertiaryTextColor: green 8 | borderColor: white 9 | stat_table: 10 | keyFgColor: yellow 11 | valueFgColor: white 12 | logoColor: yellow 13 | proc_table: 14 | fgColor: lightskyblue 15 | fgWarning: yellow 16 | fgPending: grey 17 | fgCompleted: lightgreen 18 | fgError: red 19 | headerFgColor: white 20 | help: 21 | fgColor: black 22 | keyColor: white 23 | hlColor: green 24 | categoryFgColor: lightskyblue 25 | dialog: 26 | fgColor: cadetblue 27 | bgColor: black 28 | contrastBgColor: blue 29 | attentionBgColor: red 30 | buttonFgColor: black 31 | buttonBgColor: lightskyblue 32 | buttonFocusFgColor: black 33 | buttonFocusBgColor: dodgerblue 34 | labelFgColor: yellow 35 | fieldFgColor: black 36 | fieldBgColor: lightskyblue 37 | -------------------------------------------------------------------------------- /src/config/themes/light-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: "Light Modern" 3 | body: 4 | fgColor: "#3B3B3B" 5 | bgColor: white 6 | secondaryTextColor: blue 7 | tertiaryTextColor: green 8 | borderColor: grey 9 | stat_table: 10 | keyFgColor: "#0800FF" 11 | valueFgColor: "#800000" 12 | bgColor: "#3B3B3B" 13 | logoColor: "#0800FF" 14 | proc_table: 15 | fgColor: "#0800FF" 16 | fgWarning: yellow 17 | fgPending: grey 18 | fgCompleted: green 19 | fgError: red 20 | bgColor: "#3B3B3B" 21 | headerFgColor: "#3B3B3B" 22 | help: 23 | fgColor: "#3B3B3B" 24 | keyColor: blue 25 | hlColor: lightgreen 26 | categoryFgColor: blue 27 | dialog: 28 | fgColor: cadetblue 29 | bgColor: "#3B3B3B" 30 | contrastBgColor: blue 31 | attentionBgColor: red 32 | buttonFgColor: "#3B3B3B" 33 | buttonBgColor: lightskyblue 34 | buttonFocusFgColor: "#3B3B3B" 35 | buttonFocusBgColor: dodgerblue 36 | labelFgColor: blue 37 | fieldFgColor: "#3B3B3B" 38 | fieldBgColor: lightskyblue 39 | -------------------------------------------------------------------------------- /src/config/themes/material-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'Material Dark' 3 | body: 4 | # Using standard Material Design dark theme colors 5 | fgColor: '#FFFFFF' # White (Primary Text on Dark) - Opacity ~87% conceptually 6 | bgColor: '#121212' # Recommended Dark Theme Background 7 | # Using lighter, desaturated primary color for accents on dark 8 | secondaryTextColor: '#9FA8DA' # Material Indigo 200 9 | tertiaryTextColor: '#9FA8DA' # Material Indigo 200 10 | borderColor: '#424242' # Material Grey 800 (Subtle border/divider on dark) 11 | stat_table: 12 | keyFgColor: '#9FA8DA' # Material Indigo 200 13 | valueFgColor: '#E0E0E0' # Material Grey 200 (Slightly lower emphasis text) 14 | logoColor: '#9FA8DA' # Material Indigo 200 15 | proc_table: 16 | fgColor: '#E0E0E0' # Material Grey 200 17 | # Lighter status colors for better visibility on dark backgrounds 18 | fgWarning: '#FFB74D' # Material Orange 300 19 | fgPending: '#757575' # Material Grey 600 (Low emphasis/disabled) 20 | fgCompleted: '#81C784' # Material Green 300 21 | fgError: '#E57373' # Material Red 300 22 | headerFgColor: '#9FA8DA' # Material Indigo 200 23 | help: 24 | fgColor: '#E0E0E0' # Material Grey 200 25 | keyColor: '#9FA8DA' # Material Indigo 200 26 | hlColor: '#424242' # Material Grey 800 (Highlight Background) 27 | # Using lighter accent color 28 | categoryFgColor: '#F48FB1' # Material Pink 200 29 | dialog: 30 | # Material dialogs in dark theme use elevated dark surfaces 31 | fgColor: '#FFFFFF' # White (Primary Text) 32 | bgColor: '#303030' # Material Grey 900 (Elevated Surface) 33 | # Buttons use the lighter primary color for text 34 | buttonFgColor: '#9FA8DA' # Material Indigo 200 35 | buttonBgColor: '#424242' # Material Grey 800 (Subtle Button Background) 36 | buttonFocusFgColor: '#C5CAE9' # Material Indigo 100 (Lighter for focus) 37 | buttonFocusBgColor: '#616161' # Material Grey 700 (Slightly lighter focus BG) 38 | labelFgColor: '#9FA8DA' # Material Indigo 200 (Primary color for labels) 39 | fieldFgColor: '#FFFFFF' # White (Text in fields) 40 | fieldBgColor: '#424242' # Material Grey 800 (Field Background) 41 | -------------------------------------------------------------------------------- /src/config/themes/monokai-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: Monokai 3 | body: 4 | fgColor: white 5 | bgColor: '#1E1E1E' 6 | secondaryTextColor: dodgerblue 7 | tertiaryTextColor: green 8 | borderColor: grey 9 | stat_table: 10 | keyFgColor: '#D0B344' 11 | valueFgColor: '#C5C8C6' 12 | logoColor: '#FF046D' 13 | proc_table: 14 | fgColor: '#C5C8C6' 15 | fgWarning: yellow 16 | fgPending: grey 17 | fgCompleted: lightgreen 18 | fgError: red 19 | headerFgColor: '#D0B344' 20 | help: 21 | fgColor: black 22 | keyColor: white 23 | hlColor: '#D0B344' 24 | categoryFgColor: '#9AA83A' 25 | dialog: 26 | fgColor: cadetblue 27 | bgColor: black 28 | contrastBgColor: blue 29 | attentionBgColor: red 30 | buttonFgColor: black 31 | buttonBgColor: dodgerblue 32 | buttonFocusFgColor: black 33 | buttonFocusBgColor: dodgerblue 34 | labelFgColor: yellow 35 | fieldFgColor: black 36 | fieldBgColor: '#D0B344' 37 | -------------------------------------------------------------------------------- /src/config/themes/onedark-theme.yaml: -------------------------------------------------------------------------------- 1 | style: 2 | name: 'One Dark' 3 | body: 4 | fgColor: white 5 | bgColor: '#282C34' 6 | secondaryTextColor: '#E06C75' 7 | tertiaryTextColor: green 8 | borderColor: grey 9 | stat_table: 10 | keyFgColor: '#61AFEF' 11 | valueFgColor: "#ABB2BF" 12 | logoColor: '#61AFEF' 13 | proc_table: 14 | fgColor: '#98C379' 15 | fgWarning: yellow 16 | fgPending: grey 17 | fgCompleted: green 18 | fgError: red 19 | headerFgColor: '#61AFEF' 20 | help: 21 | fgColor: black 22 | keyColor: white 23 | hlColor: '#98C379' 24 | categoryFgColor: '#C678DD' 25 | dialog: 26 | fgColor: cadetblue 27 | bgColor: black 28 | contrastBgColor: blue 29 | attentionBgColor: red 30 | buttonFgColor: black 31 | buttonBgColor: '#61AFEF' 32 | buttonFocusFgColor: black 33 | buttonFocusBgColor: dodgerblue 34 | labelFgColor: yellow 35 | fieldFgColor: black 36 | fieldBgColor: '#98C379' 37 | -------------------------------------------------------------------------------- /src/health/exec_checker.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "context" 5 | "github.com/f1bonacc1/process-compose/src/command" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type execChecker struct { 11 | command string 12 | timeout int 13 | workingDir string 14 | env []string 15 | } 16 | 17 | func (c *execChecker) Status() (interface{}, error) { 18 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.timeout)*time.Second) 19 | defer cancel() 20 | 21 | cmd := command.BuildCommandContext(ctx, c.command) 22 | cmd.SetDir(c.workingDir) 23 | cmd.SetEnv(c.env) 24 | 25 | rcMap := make(map[string]string) 26 | out, err := cmd.CombinedOutput() 27 | if err != nil { 28 | rcMap["error"] = err.Error() 29 | } 30 | rcMap["output"] = string(out) 31 | rcMap["exit_code"] = strconv.Itoa(cmd.ExitCode()) 32 | return rcMap, err 33 | } 34 | -------------------------------------------------------------------------------- /src/health/probe.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type Probe struct { 11 | Exec *ExecProbe `yaml:"exec,omitempty"` 12 | HttpGet *HttpProbe `yaml:"http_get,omitempty"` 13 | InitialDelay int `yaml:"initial_delay_seconds,omitempty"` 14 | PeriodSeconds int `yaml:"period_seconds,omitempty"` 15 | TimeoutSeconds int `yaml:"timeout_seconds,omitempty"` 16 | SuccessThreshold int `yaml:"success_threshold,omitempty"` 17 | FailureThreshold int `yaml:"failure_threshold,omitempty"` 18 | } 19 | 20 | type ExecProbe struct { 21 | Command string `yaml:"command,omitempty"` 22 | WorkingDir string `yaml:"working_dir,omitempty"` 23 | } 24 | 25 | type HttpProbe struct { 26 | Host string `yaml:"host,omitempty"` 27 | Path string `yaml:"path,omitempty"` 28 | Scheme string `yaml:"scheme,omitempty"` 29 | Port string `yaml:"port,omitempty"` 30 | NumPort int `yaml:"num_port,omitempty"` 31 | } 32 | 33 | func (h *HttpProbe) getUrl() (*url.URL, error) { 34 | urlStr := "" 35 | if h.NumPort != 0 { 36 | urlStr = fmt.Sprintf("%s://%s:%d%s", h.Scheme, h.Host, h.NumPort, h.Path) 37 | } 38 | if h.NumPort == 0 { 39 | urlStr = fmt.Sprintf("%s://%s%s", h.Scheme, h.Host, h.Path) 40 | } 41 | return url.Parse(urlStr) 42 | } 43 | 44 | func (p *Probe) ValidateAndSetDefaults() { 45 | if p.InitialDelay < 0 { 46 | p.InitialDelay = 0 47 | } 48 | if p.PeriodSeconds < 1 { 49 | p.PeriodSeconds = 10 50 | } 51 | if p.TimeoutSeconds < 1 { 52 | p.TimeoutSeconds = 1 53 | } 54 | if p.SuccessThreshold < 1 { 55 | p.SuccessThreshold = 1 56 | } 57 | if p.FailureThreshold < 1 { 58 | p.FailureThreshold = 3 59 | } 60 | 61 | if p.HttpGet != nil { 62 | p.HttpGet.validateAndSetHttpDefaults() 63 | } 64 | } 65 | 66 | func (p *HttpProbe) validateAndSetHttpDefaults() { 67 | if len(strings.TrimSpace(p.Host)) == 0 { 68 | p.Host = "127.0.0.1" 69 | } 70 | if len(strings.TrimSpace(p.Scheme)) == 0 { 71 | p.Scheme = "http" 72 | } 73 | if len(strings.TrimSpace(p.Path)) == 0 { 74 | p.Path = "/" 75 | } 76 | if p.Port == "" { 77 | p.NumPort = 0 78 | } else { 79 | p.NumPort, _ = strconv.Atoi(p.Port) 80 | } 81 | if p.NumPort < 1 || p.NumPort > 65535 { 82 | // if undefined or wrong value - will be treated as undefined 83 | p.NumPort = 0 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/loader/loader_options.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/admitter" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | type LoaderOptions struct { 11 | workingDir string 12 | FileNames []string 13 | EnvFileNames []string 14 | IsInternalLoader bool 15 | projects []*types.Project 16 | admitters []admitter.Admitter 17 | disableDotenv bool 18 | isTuiDisabled bool 19 | DryRun bool 20 | } 21 | 22 | func (o *LoaderOptions) AddAdmitter(adm ...admitter.Admitter) { 23 | o.admitters = append(o.admitters, adm...) 24 | } 25 | 26 | func (o *LoaderOptions) getWorkingDir() (string, error) { 27 | if o.workingDir != "" { 28 | return o.workingDir, nil 29 | } 30 | for _, path := range o.FileNames { 31 | if path != "-" { 32 | absPath, err := filepath.Abs(path) 33 | if err != nil { 34 | return "", err 35 | } 36 | return filepath.Dir(absPath), nil 37 | } 38 | } 39 | return os.Getwd() 40 | } 41 | 42 | func (o *LoaderOptions) DisableDotenv(disabled bool) { 43 | o.disableDotenv = disabled 44 | } 45 | 46 | func (o *LoaderOptions) WithTuiDisabled(disabled bool) { 47 | o.isTuiDisabled = disabled 48 | } 49 | -------------------------------------------------------------------------------- /src/loader/loader_options_test.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/types" 5 | "testing" 6 | ) 7 | 8 | func TestLoaderOptions_getWorkingDir(t *testing.T) { 9 | type fields struct { 10 | workingDir string 11 | FileNames []string 12 | projects []*types.Project 13 | } 14 | tests := []struct { 15 | name string 16 | fields fields 17 | want string 18 | wantErr bool 19 | }{ 20 | { 21 | name: "Empty Working Dir", 22 | fields: fields{ 23 | workingDir: "", 24 | FileNames: []string{ 25 | "/home/user/dir/process-compose.yaml", 26 | }, 27 | projects: nil, 28 | }, 29 | want: "/home/user/dir", 30 | wantErr: false, 31 | }, 32 | } 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | o := LoaderOptions{ 36 | workingDir: tt.fields.workingDir, 37 | FileNames: tt.fields.FileNames, 38 | projects: tt.fields.projects, 39 | } 40 | got, err := o.getWorkingDir() 41 | if (err != nil) != tt.wantErr { 42 | t.Errorf("getWorkingDir() error = %v, wantErr %v", err, tt.wantErr) 43 | return 44 | } 45 | if got != tt.want { 46 | t.Errorf("getWorkingDir() got = %v, want %v", got, tt.want) 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/f1bonacc1/process-compose/src/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /src/pclog/colortracker.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "hash/fnv" 6 | "sync" 7 | ) 8 | 9 | type ColorTracker struct { 10 | clrMtx sync.Mutex 11 | colors map[string]func(a ...interface{}) string 12 | maxColors int 13 | } 14 | 15 | func NewColorTracker() *ColorTracker { 16 | return &ColorTracker{ 17 | colors: map[string]func(a ...interface{}) string{}, 18 | maxColors: int(color.FgHiWhite) - int(color.FgHiBlack), 19 | } 20 | } 21 | 22 | // GetColor returns the color for the given name. 23 | func (c *ColorTracker) GetColor(name string) func(a ...interface{}) string { 24 | c.clrMtx.Lock() 25 | defer c.clrMtx.Unlock() 26 | if fn, ok := c.colors[name]; ok { 27 | return fn 28 | } 29 | fn := color.New(color.FgHiBlack+color.Attribute(stringToInt(name)%c.maxColors), color.Bold).SprintFunc() 30 | c.colors[name] = fn 31 | return fn 32 | } 33 | 34 | // Name2Color returns the color for the given name. 35 | func Name2Color(name string) func(a ...interface{}) string { 36 | maxColors := int(color.FgHiWhite) - int(color.FgHiBlack) 37 | return color.New(color.FgHiBlack+color.Attribute(stringToInt(name)%maxColors), color.Bold).SprintFunc() 38 | } 39 | 40 | func stringToInt(s string) int { 41 | // Create a hash of the string using FNV-1a algorithm 42 | hash := fnv.New32a() 43 | hash.Write([]byte(s)) 44 | 45 | // Convert the hash to an integer 46 | return int(hash.Sum32()) 47 | } 48 | -------------------------------------------------------------------------------- /src/pclog/log_observer_connector.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | type multiLineHandler func(string []string) 4 | type lineHandler func(s string) (n int, err error) 5 | 6 | type Connector struct { 7 | LogObserver 8 | logLinesHandler multiLineHandler 9 | logMessageHandler lineHandler 10 | uniqueId string 11 | taiLength int 12 | } 13 | 14 | func NewConnector(mlHandler multiLineHandler, slHandler lineHandler, tail int) *Connector { 15 | return &Connector{ 16 | logLinesHandler: mlHandler, 17 | logMessageHandler: slHandler, 18 | uniqueId: GenerateUniqueID(10), 19 | taiLength: tail, 20 | } 21 | } 22 | 23 | func (c *Connector) WriteString(s string) (n int, err error) { 24 | return c.logMessageHandler(s) 25 | } 26 | func (c *Connector) SetLines(lines []string) { 27 | c.logLinesHandler(lines) 28 | } 29 | func (c *Connector) GetUniqueID() string { 30 | return c.uniqueId 31 | } 32 | 33 | func (c *Connector) GetTailLength() int { 34 | return c.taiLength 35 | } 36 | -------------------------------------------------------------------------------- /src/pclog/logger_interface.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | type PcLogger interface { 6 | Open(filePath string, rotation *types.LoggerConfig) 7 | Info(message string, process string, replica int) 8 | Error(message string, process string, replica int) 9 | Close() 10 | } 11 | -------------------------------------------------------------------------------- /src/pclog/logs_observer.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | type LogObserver interface { 4 | WriteString(line string) (n int, err error) 5 | SetLines(lines []string) 6 | GetTailLength() int 7 | GetUniqueID() string 8 | } 9 | -------------------------------------------------------------------------------- /src/pclog/nil_logger.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | import "github.com/f1bonacc1/process-compose/src/types" 4 | 5 | type PcNilLog struct { 6 | } 7 | 8 | func NewNilLogger() *PcNilLog { 9 | 10 | return &PcNilLog{} 11 | } 12 | 13 | func (l *PcNilLog) Open(filePath string, rotation *types.LoggerConfig) { 14 | } 15 | 16 | func (l *PcNilLog) Sync() { 17 | } 18 | 19 | func (l *PcNilLog) Info(message string, process string, replica int) { 20 | 21 | } 22 | 23 | func (l *PcNilLog) Error(message string, process string, replica int) { 24 | 25 | } 26 | 27 | func (l *PcNilLog) Close() { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/pclog/process_log_buffer.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | const ( 8 | slack = 100 9 | ) 10 | 11 | type ProcessLogBuffer struct { 12 | buffer []string 13 | size int 14 | observers map[string]LogObserver 15 | mx sync.Mutex 16 | } 17 | 18 | func NewLogBuffer(size int) *ProcessLogBuffer { 19 | return &ProcessLogBuffer{ 20 | size: size, 21 | buffer: make([]string, 0, size+slack), 22 | observers: map[string]LogObserver{}, 23 | } 24 | } 25 | 26 | func (b *ProcessLogBuffer) Write(message string) { 27 | b.mx.Lock() 28 | defer b.mx.Unlock() 29 | b.buffer = append(b.buffer, message) 30 | if len(b.buffer) > b.size+slack { 31 | b.buffer = b.buffer[slack:] 32 | } 33 | for _, observer := range b.observers { 34 | _, _ = observer.WriteString(message) 35 | } 36 | 37 | } 38 | 39 | func (b *ProcessLogBuffer) GetLogRange(offsetFromEnd, limit int) []string { 40 | if len(b.buffer) == 0 { 41 | return []string{} 42 | } 43 | if offsetFromEnd < 0 { 44 | offsetFromEnd = 0 45 | } 46 | if offsetFromEnd > len(b.buffer) { 47 | offsetFromEnd = len(b.buffer) 48 | } 49 | 50 | if limit < 1 { 51 | limit = 0 52 | } 53 | if limit > len(b.buffer) { 54 | limit = len(b.buffer) 55 | } 56 | if offsetFromEnd+limit > len(b.buffer) { 57 | limit = len(b.buffer) - offsetFromEnd 58 | } 59 | if limit == 0 { 60 | return b.buffer[len(b.buffer)-offsetFromEnd:] 61 | } 62 | return b.buffer[len(b.buffer)-offsetFromEnd : offsetFromEnd+limit] 63 | } 64 | 65 | func (b *ProcessLogBuffer) GetLogLength() int { 66 | return len(b.buffer) 67 | } 68 | 69 | func (b *ProcessLogBuffer) GetLogsAndSubscribe(observer LogObserver) { 70 | b.mx.Lock() 71 | defer b.mx.Unlock() 72 | observer.SetLines(b.GetLogRange(observer.GetTailLength(), 0)) 73 | b.observers[observer.GetUniqueID()] = observer 74 | } 75 | 76 | func (b *ProcessLogBuffer) Subscribe(observer LogObserver) { 77 | b.mx.Lock() 78 | defer b.mx.Unlock() 79 | b.observers[observer.GetUniqueID()] = observer 80 | } 81 | 82 | func (b *ProcessLogBuffer) UnSubscribe(observer LogObserver) { 83 | b.mx.Lock() 84 | defer b.mx.Unlock() 85 | delete(b.observers, observer.GetUniqueID()) 86 | } 87 | 88 | func (b *ProcessLogBuffer) Close() { 89 | b.mx.Lock() 90 | defer b.mx.Unlock() 91 | b.observers = map[string]LogObserver{} 92 | } 93 | 94 | func (b *ProcessLogBuffer) Truncate() { 95 | b.mx.Lock() 96 | defer b.mx.Unlock() 97 | b.buffer = b.buffer[:0] 98 | } 99 | -------------------------------------------------------------------------------- /src/pclog/unique_id.go: -------------------------------------------------------------------------------- 1 | package pclog 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | ) 7 | 8 | func GenerateUniqueID(length int) string { 9 | if length%2 != 0 { 10 | length += 1 11 | } 12 | b := make([]byte, length/2) //each byte is 2 chars 13 | _, _ = rand.Read(b) 14 | return hex.EncodeToString(b) 15 | } 16 | -------------------------------------------------------------------------------- /src/templater/templater_test.go: -------------------------------------------------------------------------------- 1 | package templater 2 | 3 | import ( 4 | "errors" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | "testing" 7 | ) 8 | 9 | func TestTemplater_Render(t *testing.T) { 10 | t.Run("Rendering valid template", func(t *testing.T) { 11 | vars := types.Vars{"Name": "Alice", "Age": 30} 12 | templater := New(vars) 13 | result := templater.Render("Name: {{.Name}}, Age: {{.Age}}") 14 | expected := "Name: Alice, Age: 30" 15 | if result != expected { 16 | t.Errorf("Expected %s but got %s", expected, result) 17 | } 18 | }) 19 | 20 | t.Run("Rendering empty string", func(t *testing.T) { 21 | vars := types.Vars{"Key": "Value"} 22 | templater := New(vars) 23 | result := templater.Render("") 24 | if result != "" { 25 | t.Errorf("Expected an empty string but got %s", result) 26 | } 27 | }) 28 | 29 | t.Run("Rendering with error", func(t *testing.T) { 30 | vars := types.Vars{"Key": "Value"} 31 | templater := New(vars) 32 | templater.err = errors.New("Error") // Simulating an error 33 | result := templater.Render("{{.Key}}") 34 | if result != "" { 35 | t.Errorf("Expected an empty string due to error but got %s", result) 36 | } 37 | }) 38 | } 39 | 40 | func TestTemplater_RenderWithExtraVars(t *testing.T) { 41 | t.Run("Rendering with extra vars", func(t *testing.T) { 42 | vars := types.Vars{"Name": "Alice"} 43 | extraVars := types.Vars{"Age": 30} 44 | templater := New(vars) 45 | result := templater.RenderWithExtraVars("Name: {{.Name}}, Age: {{.Age}}", extraVars) 46 | expected := "Name: Alice, Age: 30" 47 | if result != expected { 48 | t.Errorf("Expected %s but got %s", expected, result) 49 | } 50 | }) 51 | t.Run("Rendering with proc conf", func(t *testing.T) { 52 | vars := types.Vars{"Name": "Alice"} 53 | 54 | procConf := &types.ProcessConfig{ 55 | ReplicaNum: 3, 56 | Command: "Name: {{.Name}}, Replica: {{.PC_REPLICA_NUM}}", 57 | } 58 | templater := New(vars) 59 | templater.RenderProcess(procConf) 60 | expected := "Name: Alice, Replica: 3" 61 | if procConf.Command != expected { 62 | t.Errorf("Expected %s but got %s", expected, procConf.Command) 63 | } 64 | }) 65 | } 66 | 67 | func TestTemplater_GetError(t *testing.T) { 68 | t.Run("Check error when rendering fails", func(t *testing.T) { 69 | vars := types.Vars{"Key": "Value"} 70 | templater := New(vars) 71 | templater.Render("{{ invalid .Key }}") 72 | err := templater.GetError() 73 | if err == nil { 74 | t.Error("Expected an error but got nil") 75 | } 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /src/tui/debug.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func (pv *pcView) dumpStyles() { 8 | f, _ := os.Create("/tmp/styles.yaml") 9 | defer f.Close() 10 | pv.styles.Dump(f) 11 | } 12 | -------------------------------------------------------------------------------- /src/tui/dialog.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | ) 6 | 7 | func (pv *pcView) showDialog(primitive tview.Primitive, width, height int) { 8 | pv.pages.AddPage(PageDialog, createDialogPage(primitive, width, height), true, true) 9 | pv.appView.SetFocus(primitive) 10 | } 11 | 12 | func createDialogPage(p tview.Primitive, width, height int) tview.Primitive { 13 | return tview.NewGrid(). 14 | SetColumns(0, width, 0). 15 | SetRows(0, height, 0). 16 | AddItem(p, 1, 1, 1, 1, 0, 0, true) 17 | } 18 | -------------------------------------------------------------------------------- /src/tui/proc-info-form.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "fmt" 5 | "github.com/f1bonacc1/process-compose/src/types" 6 | "github.com/rivo/tview" 7 | "strings" 8 | ) 9 | 10 | func (pv *pcView) createProcInfoForm(info *types.ProcessConfig, ports *types.ProcessPorts) *tview.Form { 11 | f := tview.NewForm() 12 | f.SetCancelFunc(func() { 13 | pv.pages.RemovePage(PageDialog) 14 | }) 15 | f.SetItemPadding(1) 16 | f.SetBorder(true) 17 | f.SetButtonsAlign(tview.AlignCenter) 18 | f.SetTitle("Process " + info.Name + " Info") 19 | addStringIfNotEmpty("Description:", info.Description, f) 20 | addStringIfNotEmpty("Entrypoint:", strings.Join(info.Entrypoint, " "), f) 21 | addStringIfNotEmpty("Command:", info.Command, f) 22 | addStringIfNotEmpty("Working Directory:", info.WorkingDir, f) 23 | addStringIfNotEmpty("Log Location:", info.LogLocation, f) 24 | f.AddInputField("Replica:", fmt.Sprintf("%d/%d", info.ReplicaNum+1, info.Replicas), 0, nil, nil) 25 | addDropDownIfNotEmpty("Environment:", info.Environment, f) 26 | addCSVIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f) 27 | if ports != nil { 28 | addCSVIfNotEmpty("TCP Ports:", ports.TcpPorts, f) 29 | } 30 | f.AddCheckbox("Is Disabled:", info.Disabled, nil) 31 | f.AddCheckbox("Is Daemon:", info.IsDaemon, nil) 32 | f.AddCheckbox("Is TTY:", info.IsTty, nil) 33 | f.AddCheckbox("Is Elevated:", info.IsElevated, nil) 34 | f.AddButton("Close", func() { 35 | pv.pages.RemovePage(PageDialog) 36 | }) 37 | f.SetFocus(f.GetFormItemCount()) 38 | pv.styleForm(f) 39 | return f 40 | } 41 | 42 | func addStringIfNotEmpty(label, value string, f *tview.Form) { 43 | if len(strings.TrimSpace(value)) > 0 { 44 | f.AddInputField(label, value, 0, nil, nil) 45 | } 46 | } 47 | 48 | func addDropDownIfNotEmpty(label string, value []string, f *tview.Form) { 49 | if len(value) > 0 { 50 | f.AddDropDown(label, value, 0, nil) 51 | } 52 | } 53 | 54 | func addCSVIfNotEmpty[K comparable](label string, value []K, f *tview.Form) { 55 | if len(value) > 0 { 56 | csvPorts := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(value)), ":"), "[]") 57 | f.AddInputField(label, csvPorts, 0, nil, nil) 58 | } 59 | } 60 | 61 | // mapKeysToSlice extract keys of map as slice, 62 | func mapKeysToSlice[K comparable, V any](m map[K]V) []K { 63 | keys := make([]K, len(m)) 64 | 65 | i := 0 66 | for k := range m { 67 | keys[i] = k 68 | i++ 69 | } 70 | return keys 71 | } 72 | -------------------------------------------------------------------------------- /src/tui/proc-starter.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/f1bonacc1/process-compose/src/command" 7 | "github.com/f1bonacc1/process-compose/src/types" 8 | "github.com/rs/zerolog/log" 9 | "os" 10 | "os/exec" 11 | "os/signal" 12 | "syscall" 13 | ) 14 | 15 | func (pv *pcView) startProcess() { 16 | name := pv.getSelectedProcName() 17 | info, err := pv.project.GetProcessInfo(name) 18 | if err != nil { 19 | pv.showError(err.Error()) 20 | return 21 | } 22 | if info.IsForeground { 23 | pv.runForeground(info) 24 | return 25 | } 26 | err = pv.project.StartProcess(name) 27 | if err != nil { 28 | pv.showError(err.Error()) 29 | } 30 | } 31 | 32 | func (pv *pcView) runShellProcess() { 33 | shell := command.DefaultShellConfig() 34 | shellCmd := &types.ProcessConfig{ 35 | Executable: shell.ShellCommand, 36 | RestartPolicy: types.RestartPolicyConfig{ 37 | Restart: types.RestartPolicyNo, 38 | }, 39 | } 40 | pv.runForeground(shellCmd) 41 | } 42 | 43 | func (pv *pcView) runCommandInForeground(cmd string, args ...string) { 44 | shellCmd := &types.ProcessConfig{ 45 | Executable: cmd, 46 | Args: args, 47 | RestartPolicy: types.RestartPolicyConfig{ 48 | Restart: types.RestartPolicyNo, 49 | }, 50 | } 51 | pv.runForeground(shellCmd) 52 | } 53 | 54 | func (pv *pcView) runForeground(info *types.ProcessConfig) bool { 55 | pv.halt() 56 | defer pv.resume() 57 | return pv.appView.Suspend(func() { 58 | err := pv.execute(info) 59 | if err != nil { 60 | log.Err(err).Msgf("Command failed") 61 | } 62 | }) 63 | } 64 | 65 | func (pv *pcView) execute(info *types.ProcessConfig) error { 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | defer func() { 68 | cancel() 69 | //clearScreen() 70 | }() 71 | sigChan := make(chan os.Signal, 1) 72 | signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) 73 | go func(cancel context.CancelFunc) { 74 | select { 75 | case sig := <-sigChan: 76 | log.Debug().Msgf("Command canceled with signal %#v", sig) 77 | cancel() 78 | case <-ctx.Done(): 79 | log.Debug().Msgf("Foreground process context canceled") 80 | } 81 | }(cancel) 82 | 83 | cmd := exec.CommandContext(ctx, info.Executable, info.Args...) 84 | log.Debug().Str("exec", info.Executable).Strs("args", info.Args).Msg("running start") 85 | cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr 86 | err := cmd.Run() 87 | log.Debug().Str("exec", info.Executable).Strs("args", info.Args).Msg("running end") 88 | 89 | return err 90 | } 91 | 92 | func clearScreen() { 93 | fmt.Print("\033[H\033[2J") 94 | } 95 | -------------------------------------------------------------------------------- /src/tui/proc-table_test.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMemToString(t *testing.T) { 8 | type args struct { 9 | mem int64 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | want string 15 | }{ 16 | { 17 | name: "negative", 18 | args: args{ 19 | mem: -1, 20 | }, 21 | want: "unknown", 22 | }, 23 | { 24 | name: "zero", 25 | args: args{ 26 | mem: 0, 27 | }, 28 | want: "-", 29 | }, 30 | { 31 | name: "bytes", 32 | args: args{ 33 | mem: 345, 34 | }, 35 | want: "0.0 MiB", 36 | }, 37 | { 38 | name: "kilobytes", 39 | args: args{ 40 | mem: 1024 * 513, 41 | }, 42 | want: "0.5 MiB", 43 | }, 44 | { 45 | name: "megabytes", 46 | args: args{ 47 | mem: 1024*1024*7 + 1024*513, 48 | }, 49 | want: "7.5 MiB", 50 | }, 51 | { 52 | name: "gigabytes", 53 | args: args{ 54 | mem: 1024*1024*1024*4 + 1024*1024*740, 55 | }, 56 | want: "4.7 GiB", 57 | }, 58 | } 59 | for _, tt := range tests { 60 | t.Run(tt.name, func(t *testing.T) { 61 | if got := getStrForMem(tt.args.mem, true); got != tt.want { 62 | t.Errorf("getStrForMem() = %v, want %v", got, tt.want) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/tui/scale-form.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/rs/zerolog/log" 6 | "strconv" 7 | ) 8 | 9 | func (pv *pcView) showScale() { 10 | f := tview.NewForm() 11 | f.SetCancelFunc(func() { 12 | pv.pages.RemovePage(PageDialog) 13 | }) 14 | f.SetItemPadding(3) 15 | f.SetBorder(true) 16 | name := pv.getSelectedProcName() 17 | f.SetTitle("Scale " + name + " Process") 18 | f.AddInputField("Replicas:", "1", 0, nil, nil) 19 | f.AddButton("Scale", func() { 20 | scale, err := strconv.Atoi(f.GetFormItem(0).(*tview.InputField).GetText()) 21 | if err != nil { 22 | pv.showError("Invalid Scale: " + err.Error()) 23 | return 24 | } 25 | log.Info().Msgf("Scaling %s to %d", name, scale) 26 | err = pv.project.ScaleProcess(name, scale) 27 | if err != nil { 28 | pv.showError("Invalid Scale: " + err.Error()) 29 | } 30 | pv.pages.RemovePage(PageDialog) 31 | }) 32 | f.AddButton("Cancel", func() { 33 | pv.pages.RemovePage(PageDialog) 34 | }) 35 | f.SetButtonsAlign(tview.AlignCenter) 36 | pv.styleForm(f) 37 | pv.showDialog(f, 60, 10) 38 | } 39 | -------------------------------------------------------------------------------- /src/tui/search-form.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | "github.com/rivo/tview" 6 | ) 7 | 8 | func (pv *pcView) showSearch() { 9 | const fieldWidth = 50 10 | f := tview.NewForm() 11 | f.SetCancelFunc(func() { 12 | pv.pages.RemovePage(PageDialog) 13 | }) 14 | f.SetItemPadding(1) 15 | f.SetBorder(true) 16 | f.SetButtonsAlign(tview.AlignCenter) 17 | f.SetTitle("Search Log") 18 | f.AddInputField("Search For", pv.logsText.getSearchTerm(), fieldWidth, nil, nil) 19 | f.AddCheckbox("Case Sensitive", false, nil) 20 | f.AddCheckbox("Regex", false, nil) 21 | searchFunc := func() { 22 | searchTerm := f.GetFormItem(0).(*tview.InputField).GetText() 23 | caseSensitive := f.GetFormItem(1).(*tview.Checkbox).IsChecked() 24 | isRegex := f.GetFormItem(2).(*tview.Checkbox).IsChecked() 25 | pv.stopFollowLog() 26 | if err := pv.logsText.searchString(searchTerm, isRegex, caseSensitive); err != nil { 27 | f.SetTitle(err.Error()) 28 | return 29 | } 30 | pv.pages.RemovePage(PageDialog) 31 | pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName())) 32 | pv.updateHelpTextView() 33 | } 34 | f.AddButton("Search", searchFunc) 35 | f.AddButton("Cancel", func() { 36 | pv.pages.RemovePage(PageDialog) 37 | }) 38 | f.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 39 | switch event.Key() { 40 | case tcell.KeyEnter: 41 | searchFunc() 42 | case tcell.KeyEsc: 43 | pv.pages.RemovePage(PageDialog) 44 | default: 45 | return event 46 | } 47 | return nil 48 | }) 49 | f.SetFocus(0) 50 | pv.styleForm(f) 51 | // Display and focus the dialog 52 | pv.pages.AddPage(PageDialog, createDialogPage(f, fieldWidth+20, 11), true, true) 53 | pv.appView.SetFocus(f) 54 | } 55 | -------------------------------------------------------------------------------- /src/tui/tui_option.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import "time" 4 | 5 | type Option func(view *pcView) error 6 | 7 | func WithRefreshRate(rate time.Duration) Option { 8 | return func(view *pcView) error { 9 | view.refreshRate = rate 10 | return nil 11 | } 12 | } 13 | 14 | func WithStateSorter(column ColumnID, isAscending bool) Option { 15 | return func(view *pcView) error { 16 | view.stateSorter = StateSorter{sortByColumn: column, isAsc: isAscending} 17 | return nil 18 | } 19 | } 20 | 21 | func WithTheme(theme string) Option { 22 | return func(view *pcView) error { 23 | view.themes.SelectStyles(theme) 24 | return nil 25 | } 26 | } 27 | 28 | func WithReadOnlyMode(isReadOnly bool) Option { 29 | return func(view *pcView) error { 30 | view.isReadOnlyMode = isReadOnly 31 | return nil 32 | } 33 | } 34 | 35 | func WithFullScreen(isFullScreen bool) Option { 36 | return func(view *pcView) error { 37 | view.setFullScreen(isFullScreen) 38 | return nil 39 | } 40 | } 41 | 42 | func WithDisabledHidden(isHidden bool) Option { 43 | return func(view *pcView) error { 44 | view.hideDisabled.Store(isHidden) 45 | return nil 46 | } 47 | } 48 | 49 | func WithDisabledExitConfirm(isDisabled bool) Option { 50 | return func(view *pcView) error { 51 | view.isExitConfirmDisabled = isDisabled 52 | return nil 53 | } 54 | } 55 | 56 | func WithDetachOnSuccess(detachOnSuccess bool) Option { 57 | return func(view *pcView) error { 58 | view.detachOnSuccess = detachOnSuccess 59 | return nil 60 | } 61 | } 62 | 63 | func LoadExtraShortCutsPaths(paths []string) Option { 64 | return func(view *pcView) error { 65 | for _, path := range paths { 66 | _ = view.shortcuts.loadFromFile(path) 67 | } 68 | view.recreateHelpDialog() 69 | return nil 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/types/deprecation.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/rs/zerolog/log" 5 | "os" 6 | "time" 7 | ) 8 | 9 | const ( 10 | month = time.Hour * 24 * 30 11 | ) 12 | 13 | type DeprecationParams struct { 14 | StartTime time.Time 15 | } 16 | 17 | func deprecationHandler(start, proc, deprecated, new, scope string) { 18 | startTime, _ := time.Parse("2006-01-02", start) 19 | if time.Now().Before(startTime.Add(month)) { 20 | //month not passed since start 21 | log.Warn().Msgf("Process %s uses deprecated %s '%s' please change to '%s'", proc, scope, deprecated, new) 22 | } else if time.Now().Before(startTime.Add(2 * month)) { 23 | //2 months not passed 24 | log.Error().Msgf("Process %s uses deprecated %s '%s' please change to '%s'", proc, scope, deprecated, new) 25 | time.Sleep(5 * time.Second) 26 | } else { 27 | log.Error().Msgf("Process %s uses deprecated %s '%s' please change to '%s' exiting...", proc, scope, deprecated, new) 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/types/logger.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // LogRotationConfig is the configuration for logging 4 | type LogRotationConfig struct { 5 | // Directory to log to when filelogging is enabled 6 | Directory string `yaml:"directory,omitempty"` 7 | // Filename is the name of the logfile which will be placed inside the directory 8 | Filename string `yaml:"filename,omitempty"` 9 | // MaxSize the max size in MB of the logfile before it's rolled 10 | MaxSize int `yaml:"max_size_mb,omitempty"` 11 | // MaxBackups the max number of rolled files to keep 12 | MaxBackups int `yaml:"max_backups,omitempty"` 13 | // MaxAge the max age in days to keep a logfile 14 | MaxAge int `yaml:"max_age_days,omitempty"` 15 | // Compress determines if the rotated log files should be compressed 16 | // using gzip. The default is not to perform compression. 17 | Compress bool `json:"compress" yaml:"compress,omitempty"` 18 | } 19 | 20 | type LoggerConfig struct { 21 | // Rotation is the configuration for logging rotation 22 | Rotation *LogRotationConfig `yaml:"rotation,omitempty"` 23 | // FieldsOrder is the order in which fields are logged 24 | FieldsOrder []string `yaml:"fields_order,omitempty"` 25 | // DisableJSON disables log JSON formatting 26 | DisableJSON bool `yaml:"disable_json,omitempty"` 27 | // TimestampFormat is the format of the timestamp 28 | TimestampFormat string `yaml:"timestamp_format,omitempty"` 29 | // NoColor disables coloring 30 | NoColor bool `yaml:"no_color,omitempty"` 31 | // NoMetadata disables log metadata (process, replica) 32 | NoMetadata bool `yaml:"no_metadata,omitempty"` 33 | // AddTimestamp adds timestamp to log 34 | AddTimestamp bool `yaml:"add_timestamp,omitempty"` 35 | // FlushEachLine flushes the logger on each line 36 | FlushEachLine bool `yaml:"flush_each_line,omitempty"` 37 | } 38 | -------------------------------------------------------------------------------- /src/types/processcondition_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=ProcessCondition"; DO NOT EDIT. 2 | 3 | package types 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[ProcessConditionCompleted-0] 12 | _ = x[ProcessConditionCompletedSuccessfully-1] 13 | _ = x[ProcessConditionHealthy-2] 14 | _ = x[ProcessConditionStarted-3] 15 | _ = x[ProcessConditionLogReady-4] 16 | } 17 | 18 | const _ProcessCondition_name = "ProcessConditionCompletedProcessConditionCompletedSuccessfullyProcessConditionHealthyProcessConditionStartedProcessConditionLogReady" 19 | 20 | var _ProcessCondition_index = [...]uint8{0, 25, 62, 85, 108, 132} 21 | 22 | func (i ProcessCondition) String() string { 23 | if i < 0 || i >= ProcessCondition(len(_ProcessCondition_index)-1) { 24 | return "ProcessCondition(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _ProcessCondition_name[_ProcessCondition_index[i]:_ProcessCondition_index[i+1]] 27 | } 28 | -------------------------------------------------------------------------------- /src/types/project_state.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | type ProjectState struct { 6 | FileNames []string `json:"fileNames"` 7 | UpTime time.Duration `json:"upTime" swaggertype:"primitive,integer"` 8 | StartTime time.Time `json:"startTime"` 9 | ProcessNum int `json:"processNum"` 10 | RunningProcessNum int `json:"runningProcessNum"` 11 | UserName string `json:"userName"` 12 | HostName string `json:"hostName"` 13 | Version string `json:"version"` 14 | MemoryState *MemoryState `json:"memoryState,omitempty"` 15 | } 16 | 17 | type MemoryState struct { 18 | Allocated uint64 `json:"allocated"` 19 | TotalAllocated uint64 `json:"totalAllocated"` 20 | SystemMemory uint64 `json:"systemMemory"` 21 | GcCycles uint32 `json:"gcCycles"` 22 | } 23 | -------------------------------------------------------------------------------- /src/types/restartpolicy_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=RestartPolicy"; DO NOT EDIT. 2 | 3 | package types 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[RestartPolicyNo-0] 12 | _ = x[RestartPolicyAlways-1] 13 | _ = x[RestartPolicyOnFailure-2] 14 | _ = x[RestartPolicyExitOnFailure-3] 15 | } 16 | 17 | const _RestartPolicy_name = "RestartPolicyNoRestartPolicyAlwaysRestartPolicyOnFailureRestartPolicyExitOnFailure" 18 | 19 | var _RestartPolicy_index = [...]uint8{0, 15, 34, 56, 82} 20 | 21 | func (i RestartPolicy) String() string { 22 | if i < 0 || i >= RestartPolicy(len(_RestartPolicy_index)-1) { 23 | return "RestartPolicy(" + strconv.FormatInt(int64(i), 10) + ")" 24 | } 25 | return _RestartPolicy_name[_RestartPolicy_index[i]:_RestartPolicy_index[i+1]] 26 | } 27 | -------------------------------------------------------------------------------- /src/updater/update_checker.go: -------------------------------------------------------------------------------- 1 | package updater 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | const ( 9 | //UrlPath for retrieving the latest release version 10 | UrlPath = "https://api.github.com/repos/f1bonacc1/process-compose/releases/latest" 11 | ) 12 | 13 | type Release struct { 14 | Name string `json:"name"` 15 | } 16 | 17 | func GetLatestReleaseName() (string, error) { 18 | resp, err := http.Get(UrlPath) 19 | if err != nil { 20 | return "", err 21 | } 22 | defer resp.Body.Close() 23 | var cResp Release 24 | if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil { 25 | return "", err 26 | } 27 | return cResp.Name, nil 28 | } 29 | -------------------------------------------------------------------------------- /test_loop.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | #trap "echo ERROR: The program is terminated ; exit" SIGTERM 5 | trap 'echo CODE: $?; exit $EXIT_CODE' 1 2 3 15 6 | 7 | LOOPS=3000000 8 | for (( i=1; i<=LOOPS; i++ )) 9 | do 10 | sleep 0.1 11 | #sleep 5 12 | 13 | if [[ -z "${PRINT_ERR}" ]]; then 14 | echo "test loop $i loop loop loop loop loop loop loop loop loop loop" #loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop $1 $ABC" 15 | else 16 | echo "test loop $i this is error $1 $PC_PROC_NAME" >&2 17 | fi 18 | 19 | if [[ $i -eq 7 ]]; then 20 | echo "test loop $i this is error $1 $PC_PROC_NAME - $PC_REPLICA_NUM" >&2 21 | fi 22 | 23 | done 24 | exit "$EXIT_CODE" 25 | -------------------------------------------------------------------------------- /test_loop.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | $LOOPS=3 4 | foreach($i in 1..$LOOPS) 5 | { 6 | Start-Sleep 1 7 | 8 | if (Test-Path 'env:PRINT_ERR') { 9 | 10 | # Write-Warning "test loop $i this is error $Args[0] $Env:PC_PROC_NAME" 11 | $Host.UI.WriteErrorLine("test loop $i this is error " + $Args[0] + " " + $Env:PC_PROC_NAME) 12 | } 13 | else{ 14 | Write-Host "test loop $i"$Args[0] [$Env:ABC] 15 | } 16 | } 17 | 18 | exit $Env:EXIT_CODE 19 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_attach.md: -------------------------------------------------------------------------------- 1 | ## process-compose attach 2 | 3 | Attach the Process Compose TUI Remotely to a Running Process Compose Server 4 | 5 | ``` 6 | process-compose attach [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -a, --address string address of the target process compose server (default "localhost") 13 | -h, --help help for attach 14 | -l, --log-length int log length to display in TUI (default 1000) 15 | -r, --ref-rate duration TUI refresh rate in seconds or as a Go duration string (e.g. 1s) (default 1) 16 | -R, --reverse sort in reverse order 17 | -S, --sort string sort column name. legal values (case insensitive): [AGE, CPU, EXIT, HEALTH, MEM, NAME, NAMESPACE, PID, RESTARTS, STATUS] (default "NAME") 18 | --theme string select process compose theme (default "Default") 19 | ``` 20 | 21 | ### Options inherited from parent commands 22 | 23 | ``` 24 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 25 | --no-server disable HTTP server (env: PC_NO_SERVER) 26 | --ordered-shutdown shut down processes in reverse dependency order 27 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 28 | --read-only enable read-only mode (env: PC_READ_ONLY) 29 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 30 | -U, --use-uds use unix domain sockets instead of tcp 31 | ``` 32 | 33 | ### SEE ALSO 34 | 35 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 36 | 37 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_completion.md: -------------------------------------------------------------------------------- 1 | ## process-compose completion 2 | 3 | Generate the autocompletion script for the specified shell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for process-compose for the specified shell. 8 | See each sub-command's help for details on how to use the generated script. 9 | 10 | 11 | ### Options 12 | 13 | ``` 14 | -h, --help help for completion 15 | ``` 16 | 17 | ### Options inherited from parent commands 18 | 19 | ``` 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 32 | * [process-compose completion bash](process-compose_completion_bash.md) - Generate the autocompletion script for bash 33 | * [process-compose completion fish](process-compose_completion_fish.md) - Generate the autocompletion script for fish 34 | * [process-compose completion powershell](process-compose_completion_powershell.md) - Generate the autocompletion script for powershell 35 | * [process-compose completion zsh](process-compose_completion_zsh.md) - Generate the autocompletion script for zsh 36 | 37 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_completion_bash.md: -------------------------------------------------------------------------------- 1 | ## process-compose completion bash 2 | 3 | Generate the autocompletion script for bash 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the bash shell. 8 | 9 | This script depends on the 'bash-completion' package. 10 | If it is not installed already, you can install it via your OS's package manager. 11 | 12 | To load completions in your current shell session: 13 | 14 | source <(process-compose completion bash) 15 | 16 | To load completions for every new session, execute once: 17 | 18 | #### Linux: 19 | 20 | process-compose completion bash > /etc/bash_completion.d/process-compose 21 | 22 | #### macOS: 23 | 24 | process-compose completion bash > $(brew --prefix)/etc/bash_completion.d/process-compose 25 | 26 | You will need to start a new shell for this setup to take effect. 27 | 28 | 29 | ``` 30 | process-compose completion bash 31 | ``` 32 | 33 | ### Options 34 | 35 | ``` 36 | -h, --help help for bash 37 | --no-descriptions disable completion descriptions 38 | ``` 39 | 40 | ### Options inherited from parent commands 41 | 42 | ``` 43 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 44 | --no-server disable HTTP server (env: PC_NO_SERVER) 45 | --ordered-shutdown shut down processes in reverse dependency order 46 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 47 | --read-only enable read-only mode (env: PC_READ_ONLY) 48 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 49 | -U, --use-uds use unix domain sockets instead of tcp 50 | ``` 51 | 52 | ### SEE ALSO 53 | 54 | * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell 55 | 56 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_completion_fish.md: -------------------------------------------------------------------------------- 1 | ## process-compose completion fish 2 | 3 | Generate the autocompletion script for fish 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the fish shell. 8 | 9 | To load completions in your current shell session: 10 | 11 | process-compose completion fish | source 12 | 13 | To load completions for every new session, execute once: 14 | 15 | process-compose completion fish > ~/.config/fish/completions/process-compose.fish 16 | 17 | You will need to start a new shell for this setup to take effect. 18 | 19 | 20 | ``` 21 | process-compose completion fish [flags] 22 | ``` 23 | 24 | ### Options 25 | 26 | ``` 27 | -h, --help help for fish 28 | --no-descriptions disable completion descriptions 29 | ``` 30 | 31 | ### Options inherited from parent commands 32 | 33 | ``` 34 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 35 | --no-server disable HTTP server (env: PC_NO_SERVER) 36 | --ordered-shutdown shut down processes in reverse dependency order 37 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 38 | --read-only enable read-only mode (env: PC_READ_ONLY) 39 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 40 | -U, --use-uds use unix domain sockets instead of tcp 41 | ``` 42 | 43 | ### SEE ALSO 44 | 45 | * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell 46 | 47 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_completion_powershell.md: -------------------------------------------------------------------------------- 1 | ## process-compose completion powershell 2 | 3 | Generate the autocompletion script for powershell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for powershell. 8 | 9 | To load completions in your current shell session: 10 | 11 | process-compose completion powershell | Out-String | Invoke-Expression 12 | 13 | To load completions for every new session, add the output of the above command 14 | to your powershell profile. 15 | 16 | 17 | ``` 18 | process-compose completion powershell [flags] 19 | ``` 20 | 21 | ### Options 22 | 23 | ``` 24 | -h, --help help for powershell 25 | --no-descriptions disable completion descriptions 26 | ``` 27 | 28 | ### Options inherited from parent commands 29 | 30 | ``` 31 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 32 | --no-server disable HTTP server (env: PC_NO_SERVER) 33 | --ordered-shutdown shut down processes in reverse dependency order 34 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 35 | --read-only enable read-only mode (env: PC_READ_ONLY) 36 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 37 | -U, --use-uds use unix domain sockets instead of tcp 38 | ``` 39 | 40 | ### SEE ALSO 41 | 42 | * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell 43 | 44 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_completion_zsh.md: -------------------------------------------------------------------------------- 1 | ## process-compose completion zsh 2 | 3 | Generate the autocompletion script for zsh 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the zsh shell. 8 | 9 | If shell completion is not already enabled in your environment you will need 10 | to enable it. You can execute the following once: 11 | 12 | echo "autoload -U compinit; compinit" >> ~/.zshrc 13 | 14 | To load completions in your current shell session: 15 | 16 | source <(process-compose completion zsh) 17 | 18 | To load completions for every new session, execute once: 19 | 20 | #### Linux: 21 | 22 | process-compose completion zsh > "${fpath[1]}/_process-compose" 23 | 24 | #### macOS: 25 | 26 | process-compose completion zsh > $(brew --prefix)/share/zsh/site-functions/_process-compose 27 | 28 | You will need to start a new shell for this setup to take effect. 29 | 30 | 31 | ``` 32 | process-compose completion zsh [flags] 33 | ``` 34 | 35 | ### Options 36 | 37 | ``` 38 | -h, --help help for zsh 39 | --no-descriptions disable completion descriptions 40 | ``` 41 | 42 | ### Options inherited from parent commands 43 | 44 | ``` 45 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 46 | --no-server disable HTTP server (env: PC_NO_SERVER) 47 | --ordered-shutdown shut down processes in reverse dependency order 48 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 49 | --read-only enable read-only mode (env: PC_READ_ONLY) 50 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 51 | -U, --use-uds use unix domain sockets instead of tcp 52 | ``` 53 | 54 | ### SEE ALSO 55 | 56 | * [process-compose completion](process-compose_completion.md) - Generate the autocompletion script for the specified shell 57 | 58 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_down.md: -------------------------------------------------------------------------------- 1 | ## process-compose down 2 | 3 | Stops all the running processes and terminates the Process Compose 4 | 5 | ``` 6 | process-compose down [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -a, --address string address of the target process compose server (default "localhost") 13 | -h, --help help for down 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_info.md: -------------------------------------------------------------------------------- 1 | ## process-compose info 2 | 3 | Print configuration info 4 | 5 | ``` 6 | process-compose info [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for info 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 19 | --no-server disable HTTP server (env: PC_NO_SERVER) 20 | --ordered-shutdown shut down processes in reverse dependency order 21 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 22 | --read-only enable read-only mode (env: PC_READ_ONLY) 23 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 24 | -U, --use-uds use unix domain sockets instead of tcp 25 | ``` 26 | 27 | ### SEE ALSO 28 | 29 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 30 | 31 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_list.md: -------------------------------------------------------------------------------- 1 | ## process-compose list 2 | 3 | List available processes 4 | 5 | ``` 6 | process-compose list [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for list 13 | -o, --output string Output format. One of: (json, wide) 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process.md: -------------------------------------------------------------------------------- 1 | ## process-compose process 2 | 3 | Execute operations on the available processes 4 | 5 | ### Options 6 | 7 | ``` 8 | -a, --address string address of the target process compose server (default "localhost") 9 | -h, --help help for process 10 | ``` 11 | 12 | ### Options inherited from parent commands 13 | 14 | ``` 15 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 16 | --no-server disable HTTP server (env: PC_NO_SERVER) 17 | --ordered-shutdown shut down processes in reverse dependency order 18 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 19 | --read-only enable read-only mode (env: PC_READ_ONLY) 20 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 21 | -U, --use-uds use unix domain sockets instead of tcp 22 | ``` 23 | 24 | ### SEE ALSO 25 | 26 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 27 | * [process-compose process get](process-compose_process_get.md) - Get a process state 28 | * [process-compose process list](process-compose_process_list.md) - List available processes 29 | * [process-compose process logs](process-compose_process_logs.md) - Fetch the logs of a process(es). For multiple processes, separate them with a comma (proc1,proc2) 30 | * [process-compose process ports](process-compose_process_ports.md) - Get the ports that a process is listening on 31 | * [process-compose process restart](process-compose_process_restart.md) - Restart a process 32 | * [process-compose process scale](process-compose_process_scale.md) - Scale a process to a given count 33 | * [process-compose process start](process-compose_process_start.md) - Start a process 34 | * [process-compose process stop](process-compose_process_stop.md) - Stop running processes 35 | 36 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_get.md: -------------------------------------------------------------------------------- 1 | ## process-compose process get 2 | 3 | Get a process state 4 | 5 | ``` 6 | process-compose process get [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for get 13 | -o, --output string Output format. One of: (json, wide (default)) 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -a, --address string address of the target process compose server (default "localhost") 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 32 | 33 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_list.md: -------------------------------------------------------------------------------- 1 | ## process-compose process list 2 | 3 | List available processes 4 | 5 | ``` 6 | process-compose process list [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for list 13 | -o, --output string Output format. One of: (json, wide) 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -a, --address string address of the target process compose server (default "localhost") 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 32 | 33 | ###### Auto generated by spf13/cobra on 13-Sep-2024 34 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_logs.md: -------------------------------------------------------------------------------- 1 | ## process-compose process logs 2 | 3 | Fetch the logs of a process(es). For multiple processes, separate them with a comma (proc1,proc2) 4 | 5 | ``` 6 | process-compose process logs [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -f, --follow Follow log output 13 | -h, --help help for logs 14 | -N, --namespace string Logs all the processes in the given namespace 15 | --raw-log If set, don't format the multi process log output to include the process name 16 | -n, --tail int Number of lines to show from the end of the logs (default 9223372036854775807) 17 | ``` 18 | 19 | ### Options inherited from parent commands 20 | 21 | ``` 22 | -a, --address string address of the target process compose server (default "localhost") 23 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 24 | --no-server disable HTTP server (env: PC_NO_SERVER) 25 | --ordered-shutdown shut down processes in reverse dependency order 26 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 27 | --read-only enable read-only mode (env: PC_READ_ONLY) 28 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 29 | -U, --use-uds use unix domain sockets instead of tcp 30 | ``` 31 | 32 | ### SEE ALSO 33 | 34 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 35 | * [process-compose process logs truncate](process-compose_process_logs_truncate.md) - Truncate the logs for a running or stopped process 36 | 37 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_logs_truncate.md: -------------------------------------------------------------------------------- 1 | ## process-compose process logs truncate 2 | 3 | Truncate the logs for a running or stopped process 4 | 5 | ``` 6 | process-compose process logs truncate [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for truncate 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -a, --address string address of the target process compose server (default "localhost") 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose process logs](process-compose_process_logs.md) - Fetch the logs of a process(es). For multiple processes, separate them with a comma (proc1,proc2) 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_ports.md: -------------------------------------------------------------------------------- 1 | ## process-compose process ports 2 | 3 | Get the ports that a process is listening on 4 | 5 | ``` 6 | process-compose process ports [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for ports 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -a, --address string address of the target process compose server (default "localhost") 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_restart.md: -------------------------------------------------------------------------------- 1 | ## process-compose process restart 2 | 3 | Restart a process 4 | 5 | ``` 6 | process-compose process restart [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for restart 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -a, --address string address of the target process compose server (default "localhost") 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_scale.md: -------------------------------------------------------------------------------- 1 | ## process-compose process scale 2 | 3 | Scale a process to a given count 4 | 5 | ``` 6 | process-compose process scale [PROCESS] [COUNT] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for scale 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -a, --address string address of the target process compose server (default "localhost") 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_start.md: -------------------------------------------------------------------------------- 1 | ## process-compose process start 2 | 3 | Start a process 4 | 5 | ``` 6 | process-compose process start [PROCESS] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for start 13 | ``` 14 | 15 | ### Options inherited from parent commands 16 | 17 | ``` 18 | -a, --address string address of the target process compose server (default "localhost") 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 31 | 32 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_process_stop.md: -------------------------------------------------------------------------------- 1 | ## process-compose process stop 2 | 3 | Stop running processes 4 | 5 | ``` 6 | process-compose process stop [PROCESS...] [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for stop 13 | -v, --verbose verbose output 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -a, --address string address of the target process compose server (default "localhost") 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose process](process-compose_process.md) - Execute operations on the available processes 32 | 33 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_project.md: -------------------------------------------------------------------------------- 1 | ## process-compose project 2 | 3 | Execute operations on a running Process Compose project 4 | 5 | ### Options 6 | 7 | ``` 8 | -a, --address string address of the target process compose server (default "localhost") 9 | -h, --help help for project 10 | ``` 11 | 12 | ### Options inherited from parent commands 13 | 14 | ``` 15 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 16 | --no-server disable HTTP server (env: PC_NO_SERVER) 17 | --ordered-shutdown shut down processes in reverse dependency order 18 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 19 | --read-only enable read-only mode (env: PC_READ_ONLY) 20 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 21 | -U, --use-uds use unix domain sockets instead of tcp 22 | ``` 23 | 24 | ### SEE ALSO 25 | 26 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 27 | * [process-compose project is-ready](process-compose_project_is-ready.md) - Check if Process Compose project is ready (or wait for it to be ready) 28 | * [process-compose project state](process-compose_project_state.md) - Get Process Compose project state 29 | * [process-compose project update](process-compose_project_update.md) - Update an already running process-compose instance by passing an updated process-compose.yaml file 30 | 31 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_project_is-ready.md: -------------------------------------------------------------------------------- 1 | ## process-compose project is-ready 2 | 3 | Check if Process Compose project is ready (or wait for it to be ready) 4 | 5 | ``` 6 | process-compose project is-ready [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for is-ready 13 | --wait Wait for the project to be ready instead of exiting with an error 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -a, --address string address of the target process compose server (default "localhost") 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose project](process-compose_project.md) - Execute operations on a running Process Compose project 32 | 33 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_project_state.md: -------------------------------------------------------------------------------- 1 | ## process-compose project state 2 | 3 | Get Process Compose project state 4 | 5 | ``` 6 | process-compose project state [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for state 13 | --with-memory check memory usage 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -a, --address string address of the target process compose server (default "localhost") 20 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 21 | --no-server disable HTTP server (env: PC_NO_SERVER) 22 | --ordered-shutdown shut down processes in reverse dependency order 23 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 24 | --read-only enable read-only mode (env: PC_READ_ONLY) 25 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 26 | -U, --use-uds use unix domain sockets instead of tcp 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [process-compose project](process-compose_project.md) - Execute operations on a running Process Compose project 32 | 33 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_project_update.md: -------------------------------------------------------------------------------- 1 | ## process-compose project update 2 | 3 | Update an already running process-compose instance by passing an updated process-compose.yaml file 4 | 5 | ``` 6 | process-compose project update [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -f, --config stringArray path to config files to load (env: PC_CONFIG_FILES) 13 | -h, --help help for update 14 | -n, --namespace stringArray run only specified namespaces (default all) 15 | -v, --verbose verbose output 16 | ``` 17 | 18 | ### Options inherited from parent commands 19 | 20 | ``` 21 | -a, --address string address of the target process compose server (default "localhost") 22 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 23 | --no-server disable HTTP server (env: PC_NO_SERVER) 24 | --ordered-shutdown shut down processes in reverse dependency order 25 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 26 | --read-only enable read-only mode (env: PC_READ_ONLY) 27 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 28 | -U, --use-uds use unix domain sockets instead of tcp 29 | ``` 30 | 31 | ### SEE ALSO 32 | 33 | * [process-compose project](process-compose_project.md) - Execute operations on a running Process Compose project 34 | 35 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_run.md: -------------------------------------------------------------------------------- 1 | ## process-compose run 2 | 3 | Run PROCESS in the foreground, and its dependencies in the background 4 | 5 | ### Synopsis 6 | 7 | Run selected process with std(in|out|err) attached, while other processes run in the background. 8 | Command line arguments, provided after --, are passed to the PROCESS. 9 | 10 | ``` 11 | process-compose run PROCESS [flags] -- [process_args] 12 | ``` 13 | 14 | ### Options 15 | 16 | ``` 17 | -f, --config stringArray path to config files to load (env: PC_CONFIG_FILES) 18 | --disable-dotenv disable .env file loading (env: PC_DISABLE_DOTENV=1) 19 | -h, --help help for run 20 | --no-deps don't start dependent processes 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 27 | --no-server disable HTTP server (env: PC_NO_SERVER) 28 | --ordered-shutdown shut down processes in reverse dependency order 29 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 30 | --read-only enable read-only mode (env: PC_READ_ONLY) 31 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 32 | -U, --use-uds use unix domain sockets instead of tcp 33 | ``` 34 | 35 | ### SEE ALSO 36 | 37 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 38 | 39 | -------------------------------------------------------------------------------- /www/docs/cli/process-compose_version.md: -------------------------------------------------------------------------------- 1 | ## process-compose version 2 | 3 | Print version and build info 4 | 5 | ``` 6 | process-compose version [flags] 7 | ``` 8 | 9 | ### Options 10 | 11 | ``` 12 | -h, --help help for version 13 | --short Print only version 14 | ``` 15 | 16 | ### Options inherited from parent commands 17 | 18 | ``` 19 | -L, --log-file string Specify the log file path (env: PC_LOG_FILE) (default "/tmp/process-compose-.log") 20 | --no-server disable HTTP server (env: PC_NO_SERVER) 21 | --ordered-shutdown shut down processes in reverse dependency order 22 | -p, --port int port number (env: PC_PORT_NUM) (default 8080) 23 | --read-only enable read-only mode (env: PC_READ_ONLY) 24 | -u, --unix-socket string path to unix socket (env: PC_SOCKET_PATH) (default "/tmp/process-compose-.sock") 25 | -U, --use-uds use unix domain sockets instead of tcp 26 | ``` 27 | 28 | ### SEE ALSO 29 | 30 | * [process-compose](process-compose.md) - Processes scheduler and orchestrator 31 | 32 | -------------------------------------------------------------------------------- /www/docs/contributing.md: -------------------------------------------------------------------------------- 1 | ## Set up your machine 2 | Process Compose is written in Go. 3 | 4 | ### Prerequisites: 5 | 6 | - [Make](https://www.gnu.org/software/make/) 7 | - [Go 1.22+](https://go.dev/doc/install) 8 | 9 | ### Clone Process Compose: 10 | 11 | ``` 12 | git clone git@github.com:F1bonacc1/process-compose.git 13 | ``` 14 | 15 | `cd` into the directory and install the dependencies: 16 | 17 | ``` 18 | go mod tidy 19 | ``` 20 | 21 | You should then be able to build the binary: 22 | 23 | ``` 24 | make build 25 | ``` 26 | 27 | ## Test your change 28 | 29 | You can create a branch for your changes and try to build from the source as you go. 30 | 31 | When you are satisfied with the changes, we suggest you run: 32 | 33 | ``` 34 | make ci 35 | ``` 36 | 37 | ## Create a commit 38 | 39 | Commit messages should be well formatted, and to make that "standardized", we are using Conventional Commits. 40 | 41 | You can follow the documentation on [their website](https://www.conventionalcommits.org/). 42 | 43 | ## Submit a pull request 44 | 45 | Push your branch to your `process-compose` fork and open a pull request against the main branch. 46 | 47 | -------------------------------------------------------------------------------- /www/docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/F1bonacc1/process-compose/4397f98dcf8999696352e6c5d6f2292e71967707/www/docs/img/favicon.ico -------------------------------------------------------------------------------- /www/docs/index.md: -------------------------------------------------------------------------------- 1 | # Process Compose 🔥 2 | 3 | Process Compose is a simple and flexible scheduler and orchestrator to manage non-containerized applications. 4 | 5 | ## Why was it made? 6 | 7 | Because sometimes you just don't want to deal with docker files, volume definitions, networks and docker registries. 8 | Since it's written in Go, Process Compose is a single binary file and has no other dependencies. 9 | 10 | Once [installed](installation.md), you just need to describe your workflow using a simple [YAML](http://yaml.org/) schema in a file called `process-compose.yaml`: 11 | 12 | ```yaml 13 | version: "0.5" 14 | 15 | processes: 16 | hello: 17 | command: echo 'Hello World from Process Compose' 18 | ``` 19 | 20 | And start it by running `process-compose up` from your terminal. 21 | 22 | Check the [Documentation](launcher.md) for more advanced use cases. 23 | 24 | TUI 25 | 26 | ## Features 27 | 28 | - Processes execution (in parallel or/and serially) 29 | - Processes dependencies and startup order 30 | - Process recovery policies 31 | - Manual process [re]start 32 | - Processes arguments `bash` or `zsh` style (or define your own shell) 33 | - Per process and global environment variables 34 | - Per process or global (single file) logs 35 | - Health checks (liveness and readiness) 36 | - Terminal User Interface (TUI) or CLI modes 37 | - Forking (services or daemons) processes 38 | - REST API (OpenAPI a.k.a Swagger) 39 | - Logs caching 40 | - Functions as both server and client 41 | - Configurable shortcuts 42 | - Merge Configuration Files 43 | - Namespaces 44 | - Run Multiple Replicas of a Process 45 | - Run a Foreground Process 46 | - Themes Support 47 | - On the fly Process configuration edit 48 | - On the fly Project update 49 | -------------------------------------------------------------------------------- /www/docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Download the Binary 4 | 5 | ### Binary 6 | Go to the [releases](https://github.com/F1bonacc1/process-compose/releases/latest), download the package for your OS, and add to your `$PATH`. 7 | 8 | ### Install Script 9 | Install script which is very useful in scenarios like CI. Many thanks to GoDownloader for enabling the easy generation of this script. 10 | 11 | By default, it installs on the `./bin` directory relative to the working directory: 12 | 13 | ```shell 14 | sh -c "$(curl --location https://raw.githubusercontent.com/F1bonacc1/process-compose/main/scripts/get-pc.sh)" -- -d 15 | ``` 16 | 17 | It is possible to override the installation directory with the `-b` parameter. On Linux, common choices are `~/.local/bin` and `~/bin` to install for the current user or `/usr/local/bin` to install for all users: 18 | 19 | ```shell 20 | sh -c "$(curl --location https://raw.githubusercontent.com/F1bonacc1/process-compose/main/scripts/get-pc.sh)" -- -d -b ~/.local/bin 21 | ``` 22 | !!! warning "Caution" 23 | On macOS and Windows, `~/.local/bin` and `~/bin` are not added to `$PATH` by default. 24 | 25 | ## Nix 26 | If you have the Nix package manager installed with Flake support, just run: 27 | 28 | ```sh 29 | # to use the latest binary release 30 | nix run nixpkgs/master#process-compose -- --help 31 | 32 | # or to compile from the latest source 33 | nix run github:F1bonacc1/process-compose -- --help 34 | ``` 35 | 36 | To use process-compose declaratively configured in your project `flake.nix`, checkout [process-compose-flake](https://github.com/Platonic-Systems/process-compose-flake). 37 | 38 | ## Brew (MacOS and Linux) 39 | 40 | ```shell 41 | brew install f1bonacc1/tap/process-compose 42 | ``` 43 | -------------------------------------------------------------------------------- /www/docs/intro.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Quick Start 4 | 5 | Imaginary system diagram: 6 | 7 | ![Diagram](https://github.com/F1bonacc1/process-compose/raw/main/imgs/diagram.png) 8 | 9 | `process-compose.yaml` definitions for the system above: 10 | 11 | ```yaml 12 | version: "0.5" 13 | 14 | environment: 15 | - "GLOBAL_ENV_VAR=1" 16 | log_location: /path/to/combined/output/logfile.log 17 | log_level: debug 18 | 19 | processes: 20 | Manager: 21 | command: "/path/to/manager" 22 | availability: 23 | restart: "always" 24 | depends_on: 25 | ClientA: 26 | condition: process_started 27 | ClientB: 28 | condition: process_started 29 | 30 | ClientA: 31 | command: "/path/to/ClientA" 32 | availability: 33 | restart: "always" 34 | depends_on: 35 | Server_1A: 36 | condition: process_started 37 | Server_2A: 38 | condition: process_started 39 | environment: 40 | - "LOCAL_ENV_VAR=1" 41 | 42 | ClientB: 43 | command: "/path/to/ClientB -some -arg" 44 | availability: 45 | restart: "always" 46 | depends_on: 47 | Server_1B: 48 | condition: process_started 49 | Server_2B: 50 | condition: process_started 51 | environment: 52 | - "LOCAL_ENV_VAR=2" 53 | 54 | Server_1A: 55 | command: "/path/to/Server_1A" 56 | availability: 57 | restart: "always" 58 | 59 | Server_2A: 60 | command: "/path/to/Server_2A" 61 | availability: 62 | restart: "always" 63 | 64 | Server_1B: 65 | command: "/path/to/Server_1B" 66 | availability: 67 | restart: "always" 68 | 69 | Server_2B: 70 | command: "/path/to/Server_2B" 71 | availability: 72 | restart: "always" 73 | ``` 74 | 75 | Finally, run `process-compose` in the `process-compose.yaml` directory. Or give it a direct path: 76 | 77 | ```shell 78 | process-compose -f /path/to/process-compose.yaml 79 | ``` -------------------------------------------------------------------------------- /www/docs/sponsors.md: -------------------------------------------------------------------------------- 1 | Do you or your company use Process Compose? You can help keep the project bug-free and feature rich by sponsoring the project and its contributors. 2 | 3 | ## GitHub Sponsors 4 | 5 | GitHub Sponsors is a great way to contribute directly to the Process Compose maintainer, [f1bonnacc1](https://github.com/F1bonacc1). 6 | 7 | This money usually goes to buying coffee, snacks, better hardware, and, hopefully, one day, paying the bills. 8 | 9 | You can sponsor and see who's sponsoring f1bonacc1 [here](https://github.com/sponsors/f1bonacc1). 10 | 11 | 12 | 13 | And, of course, you can also sponsor any of the [contributors](https://github.com/F1bonacc1/process-compose/graphs/contributors)! 14 | 15 | ------ 16 | 17 | **🔥🔥 Thanks for your support! 🔥🔥** --------------------------------------------------------------------------------